Проверить instanceof в потоке

80

У меня есть такое выражение:

scheduleIntervalContainers.stream()
        .filter(sic -> ((ScheduleIntervalContainer) sic).getStartTime() != ((ScheduleIntervalContainer)sic).getEndTime())
        .collect(Collectors.toList());

... где scheduleIntervalContainersесть тип элемента ScheduleContainer:

final List<ScheduleContainer> scheduleIntervalContainers

Можно ли перед фильтром проверить тип?

кума
источник

Ответы:

132

Вы можете применить другой filter, чтобы сохранить только ScheduleIntervalContainerэкземпляры, и добавление mapсохранит вам последующие приведения:

scheduleIntervalContainers.stream()
    .filter(sc -> sc instanceof ScheduleIntervalContainer)
    .map (sc -> (ScheduleIntervalContainer) sc)
    .filter(sic -> sic.getStartTime() != sic.getEndTime())
    .collect(Collectors.toList());

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

scheduleIntervalContainers.stream()
    .filter(ScheduleIntervalContainer.class::isInstance)
    .map (ScheduleIntervalContainer.class::cast)
    .filter(sic -> sic.getStartTime() != sic.getEndTime())
    .collect(Collectors.toList());
Эран
источник
123
Или в .filter(ScheduleIntervalContainer.class::isInstance) .map(ScheduleIntervalContainer.class::cast)любом другом стиле.
Holger
В IDEA и Java8, если приведенный выше фрагмент назначен List <ScheduleContainer> scheduleIntervalContainers, он по-прежнему предлагает мне явно привести результат к List <ScheduleContainer> scheduleIntervalContainers, знаете ли вы, почему?
K. Symbol
@ K.Symbol Вы пытались назначить a List<ScheduleContainer>или a List<ScheduleIntervalContainer>? Это должно быть последнее.
Эран
119

Довольно элегантный вариант - использовать ссылку на метод класса:

scheduleIntervalContainers
  .stream()
  .filter( ScheduleIntervalContainer.class::isInstance )
  .map( ScheduleIntervalContainer.class::cast )
  .filter( sic -> sic.getStartTime() != sic.getEndTime())
  .collect(Collectors.toList() );
Рикардо Гаска
источник
Какие преимущества дает этот стиль по сравнению с использованием instanceof и (ScheduleIntervalContainer) для приведения?
MageWind
@MageWind - это в основном вопрос стиля. Некоторые люди предпочитают это, потому что вам не нужно вводить другое имя переменной (для параметра лямбда), другие, потому что он генерирует немного меньший байтовый код (хотя разница недостаточна, чтобы быть действительно актуальной).
Хольгер
Это действительно круто! Но зачем это .classнужно? не является isInstanceчастью Object? Это Classкласс на Java?
Сообщение Self
@PostSelf Действительно, это и ScheduleIntervalContainerне будет экземпляром.
Наман
15

Существует небольшая проблема с @ Эран решение - имя класса набрав в обоих filterи mapподвержен ошибкам - это легко забыть изменить имя класса в обоих местах. Улучшенное решение будет примерно таким:

private static <T, R> Function<T, Stream<R>> select(Class<R> clazz) {
    return e -> clazz.isInstance(e) ? Stream.of(clazz.cast(e)) : null;
}

scheduleIntervalContainers
  .stream()
  .flatMap(select(ScheduleIntervalContainer.class))
  .filter( sic -> sic.getStartTime() != sic.getEndTime())
  .collect(Collectors.toList());   

Однако создание Streamдля каждого совпадающего элемента может привести к снижению производительности. Будьте осторожны, используя его на огромных наборах данных. Я узнал об этом решении от @ Tagir Vailev

Андрей
источник
В этом подходе вы должны позаботиться об исключениях NullPointerExceptions, поскольку select(A.class)вернет nullвсе, что не является A. Добавление .filter(Objects::nonNull)поможет. Кстати: подход @Eran нулевой.
Ларс Генднер
Извините, мой плохой ... JavaDoc flatMapговорит: "Если отображаемый поток равен нулю, вместо этого используется пустой поток.". Итак, ваше решение было правильным, даже без нулевого фильтра.
Ларс Генднер
3
Тем не менее (IMO) странно возвращаться, nullкогда вы могли только что вернуть пустой поток
Алованиак