Java8 Потоковая иерархия классов

Я медленно изучаю новые функции Java 8 и пытаюсь найти способ обработки иерархии классов (от дочерней к родительской) в виде потока.

Например, найдите аннотацию для класса или его родителей.

До Java 8 я бы сделал это так:

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;
}

Теперь я хочу сделать это более "функциональным программированием". Я не смог найти лучшего способа, чем объединить потоки с рекурсивной записью:

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);
    }
}

Но я не совсем доволен решением. Хотя я не тестировал его, я думаю, что конкатенация потока довольно громоздка и неэффективна.

Есть ли лучший способ превратить рекурсив в поток?

1 ответ

Решение

В Java 9 вы, вероятно, использовали бы

public static Stream<Class<?>> streamSuperclass(Class<?> type) {
    return Stream.iterate(type, Objects::nonNull, Class::getSuperclass);
}

но в Java 8 эта функция недоступна, поэтому вы можете прибегнуть к реализации Stream вручную:

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);
}

Обратите внимание, что это будет поток от самого определенного типа к java.lang.Object, Если вы хотите, чтобы заказ был от Object для наиболее конкретного случая нет никакого способа собрать сначала элементы, будь то рекурсивные или итеративные, это не так важно, но Stream.concat действительно наименее производительный вариант. Вы можете просто использовать

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);
}

Итерационный вариант также не так уж и плох:

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();
}

Для потоков, таких как типичная иерархия классов, ArrayList не хуже чем Stream.Builder и для очень больших потоков заполнение компоновщика с помощью рекурсии также может оказаться не лучшим решением...

Другие вопросы по тегам