Можно ли привести поток в Java 8?

160

Можно ли привести поток в Java 8? Скажем, у меня есть список объектов, я могу сделать что-то вроде этого, чтобы отфильтровать все дополнительные объекты:

Stream.of(objects).filter(c -> c instanceof Client)

Однако после этого, если я хочу что-то сделать с клиентами, мне нужно разыграть каждого из них:

Stream.of(objects).filter(c -> c instanceof Client)
    .map(c -> ((Client) c).getID()).forEach(System.out::println);

Это выглядит немного некрасиво. Можно ли привести весь поток к другому типу? Как бросить Stream<Object>к Stream<Client>?

Пожалуйста, игнорируйте тот факт, что подобные вещи, вероятно, означают плохой дизайн. Мы занимаемся такими вещами на моем уроке информатики, поэтому я изучал новые функции Java 8 и мне было любопытно, возможно ли это.

Phiction
источник
3
С точки зрения среды выполнения Java два типа потока уже совпадают, поэтому приведение не требуется. Хитрость заключается в том, чтобы прокрасться мимо компилятора. (То есть, предполагая, что в этом есть какой-то смысл.)
Hot Licks

Ответы:

283

Я не думаю, что есть способ сделать это из коробки. Возможно более чистое решение было бы:

Stream.of(objects)
    .filter(c -> c instanceof Client)
    .map(c -> (Client) c)
    .map(Client::getID)
    .forEach(System.out::println);

или, как предлагается в комментариях, вы можете использовать castметод - первый может быть легче читать, хотя:

Stream.of(objects)
    .filter(Client.class::isInstance)
    .map(Client.class::cast)
    .map(Client::getID)
    .forEach(System.out::println);
assylias
источник
Это в значительной степени то, что я искал. Я думаю, я упустил из виду, что приведение его к клиенту mapвернет a Stream<Client>. Спасибо!
Фантастика
+1 интересные новые способы, хотя они рискуют попасть в спагетти-код типа нового поколения (горизонтальный, а не вертикальный)
robermann
@LordOfThePigs Да, это работает, хотя я не уверен, станет ли код более понятным. Я добавил идею в свой ответ.
assylias
38
Вы можете «упростить» фильтр instanceOf с помощью:Stream.of(objects).filter(Client.class::isInstance).[...]
Николас Лаброт,
Часть без стрелки действительно прекрасна <3
Фабич
14

В соответствии с ответом ggovan я делаю это следующим образом:

/**
 * Provides various high-order functions.
 */
public final class F {
    /**
     * When the returned {@code Function} is passed as an argument to
     * {@link Stream#flatMap}, the result is a stream of instances of
     * {@code cls}.
     */
    public static <E> Function<Object, Stream<E>> instancesOf(Class<E> cls) {
        return o -> cls.isInstance(o)
                ? Stream.of(cls.cast(o))
                : Stream.empty();
    }
}

Используя эту вспомогательную функцию:

Stream.of(objects).flatMap(F.instancesOf(Client.class))
        .map(Client::getId)
        .forEach(System.out::println);
Brandon
источник
10

Поздно на вечеринку, но я думаю, что это полезный ответ.

flatMap был бы самый короткий способ сделать это.

Stream.of(objects).flatMap(o->(o instanceof Client)?Stream.of((Client)o):Stream.empty())

Если oэто, Clientто создайте поток с одним элементом, в противном случае используйте пустой поток. Эти потоки будут затем сплющены в Stream<Client>.

ggovan
источник
Я пытался реализовать это, но получил предупреждение о том, что мой класс "использует непроверенные или небезопасные операции" - это того, что ожидать?
Авейбелл
К сожалению, да. Если бы вы использовали if/elseвместо ?:оператора, то не было бы никакого предупреждения. Будьте уверены, что вы можете безопасно подавить предупреждение.
ggovan
3
На самом деле это дольше, чем Stream.of(objects).filter(o->o instanceof Client).map(o -> (Client)o)даже Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast).
Дидье Л
4

Это выглядит немного некрасиво. Можно ли привести весь поток к другому типу? Как бросить Stream<Object>к Stream<Client>?

Нет, это было бы невозможно. Это не ново в Java 8. Это характерно для дженериков. А List<Object>не супер тип List<String>, так что вы не можете просто привести List<Object>к List<String>.

Подобная проблема здесь. Вы не можете бросить Stream<Object>на Stream<Client>. Конечно, вы можете использовать его косвенно следующим образом:

Stream<Client> intStream = (Stream<Client>) (Stream<?>)stream;

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

Кстати, что не так с вашим подходом? Выглядит хорошо для меня.

Рохит Джайн
источник
2
@DR Generics in C#реализован с использованием reification, в то время как в Java он реализован с использованием стирания. Оба реализованы по-разному в основе. Таким образом, вы не можете ожидать, что он будет работать одинаково на обоих языках.
Рохит Джайн
1
@DR Я понимаю, что стирание создает много проблем для начинающих, чтобы понять концепцию обобщений в Java. И поскольку я не использую C #, я не могу вдаваться в подробности сравнения. Но единственной мотивацией для его реализации таким образом было то, что IMO избежало серьезных изменений в реализации JVM.
Рохит Джайн
1
Почему бы это "безусловно, потерпит неудачу во время выполнения"? Как вы говорите, нет (обобщенной) информации о типе, так что нечего проверять во время выполнения. Может произойти сбой во время выполнения, если пропущены неправильные типы, но в этом нет никакой «уверенности».
Hot Licks
1
@RohitJain: я не критикую общую концепцию Java, но это единственное последствие все еще создает уродливый код ;-)
DR
1
@DR - дженерики Java безобразны с самого начала. По большей части просто жажда C ++.
Hot Licks