This question already has answers here:
Closed 3 years ago.
I am slowly learning the new Java 8 features and I am trying to find a way to process a class hierarchy (from child to parent) as a stream.
For instance find an annotation on a class or it's parents.
Before Java 8, I would have done it this way :
public static <T extends Annotation> T getAnnonationOn(Class<?> type, Class<T> annType) {
Class<?> t = type;
T annot = null;
while (t != null && annot == null) {
annot = t.getAnnotation(annType);
t = t.getSuperclass();
}
return annot;
}
Now I wish to do it with a more "functional programming" way.
I could not find a better way than concatenate streams with a recursive like follow :
import java.lang.annotation.Annotation;
import java.util.stream.Stream;
public static <T extends Annotation> T getAnnonationOn(Class<?> type, Class<T> annType) {
return ClassIterator.streamSuperclass(type)
.map(t -> t.getAnnotation(annType))
.filter(a -> a != null)
.findFirst()
.orElse(null);
}
public static class ClassIterator {
public static Stream<Class<?>> streamSuperclass(Class<?> type) {
if (type.getSuperclass() != null) {
return Stream.concat(Stream.of(type), Stream.of(type.getSuperclass()).flatMap(ClassIterator::streamSuperclass));
}
return Stream.of(type);
}
}
But I am not quite satisfied of the solution. Although I did not benchmark it I think the stream concatenation is quite cumbersome and under performant.
Is there a better way to turn a recursive into a stream ?
In Java 9, you would probably use
public static Stream<Class<?>> streamSuperclass(Class<?> type) {
return Stream.iterate(type, Objects::nonNull, Class::getSuperclass);
}
but in Java 8, this feature is not available, so you may resort to implement the Stream manually:
public static Stream<Class<?>> streamSuperclass(Class<?> type) {
return StreamSupport.stream(
new Spliterators.AbstractSpliterator<Class<?>>(100L,
Spliterator.ORDERED|Spliterator.IMMUTABLE|Spliterator.NONNULL) {
Class<?> current = type;
public boolean tryAdvance(Consumer<? super Class<?>> action) {
if(current == null) return false;
action.accept(current);
current = current.getSuperclass();
return true;
}
}, false);
}
Note that this will stream from the most specific type towards java.lang.Object
. If you want the order to be from Object
to the most specific one, there is no way around gathering the elements first, whether recursive or iterative, is not so important, but Stream.concat
is indeed the least performance variant. You can simply use
public static Stream<Class<?>> streamSuperclass(Class<?> type) {
return reverse(Stream.<Class<?>>builder(), type, Class::getSuperclass).build();
}
private static <T> Stream.Builder<T> reverse(
Stream.Builder<T> builder, T t, UnaryOperator<T> op) {
return t==null? builder: reverse(builder, op.apply(t), op).add(t);
}
The iterative variant also is not so bad:
public static Stream<Class<?>> streamSuperclass(Class<?> type) {
List<Class<?>> l=new ArrayList<>();
for(; type!=null; type=type.getSuperclass()) l.add(type);
Collections.reverse(l);
return l.stream();
}
For streams as small as a typical class hierarchy, an ArrayList
is not worse than a Stream.Builder
and for very large streams, filling the builder using a recursion might not be the best solution either…