Verifique instanceof en la secuencia

80

Tengo la siguiente expresión:

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

... donde scheduleIntervalContainerstiene el tipo de elemento ScheduleContainer:

final List<ScheduleContainer> scheduleIntervalContainers

¿Es posible comprobar el tipo antes del filtro?

quma
fuente

Respuestas:

132

Puede aplicar otro filterpara mantener solo las ScheduleIntervalContainerinstancias, y agregar un maple ahorrará los últimos lanzamientos:

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

O, como comentó Holger, puede reemplazar las expresiones lambda con referencias de método si prefiere ese estilo:

scheduleIntervalContainers.stream()
    .filter(ScheduleIntervalContainer.class::isInstance)
    .map (ScheduleIntervalContainer.class::cast)
    .filter(sic -> sic.getStartTime() != sic.getEndTime())
    .collect(Collectors.toList());
Eran
fuente
122
O el .filter(ScheduleIntervalContainer.class::isInstance) .map(ScheduleIntervalContainer.class::cast)estilo que prefieras.
Holger
En IDEA y Java8, si el fragmento anterior se asigna a List <ScheduleContainer> scheduleIntervalContainers, todavía me pide que envíe el resultado a List <ScheduleContainer> scheduleIntervalContainers explícitamente, ¿sabe por qué?
K. Symbol
@ K.Symbol ¿Intentó asignar a a List<ScheduleContainer>o a List<ScheduleIntervalContainer>? Debería ser lo último.
Eran
119

Una opción bastante elegante es usar la referencia de método de la clase:

scheduleIntervalContainers
  .stream()
  .filter( ScheduleIntervalContainer.class::isInstance )
  .map( ScheduleIntervalContainer.class::cast )
  .filter( sic -> sic.getStartTime() != sic.getEndTime())
  .collect(Collectors.toList() );
Ricardo Gasca
fuente
¿Qué beneficio tiene este estilo en comparación con el uso de instanceof y (ScheduleIntervalContainer) para emitir?
MageWind
@MageWind eso es principalmente una cuestión de estilo. Algunas personas lo prefieren, porque no es necesario introducir otro nombre de variable (para el parámetro lambda), otras, porque genera un código de bytes ligeramente menor (aunque no hay suficiente diferencia para ser realmente relevante).
Holger
¡Esto es realmente genial! Pero, ¿por qué es .classnecesario? no es isInstanceparte de Object? ¿Es Classuna clase en Java?
Publicación propia el
@PostSelf De hecho, es y ScheduleIntervalContainerno sería realmente una instancia.
Naman
15

Hay un pequeño problema con la solución @ Eran : escribir el nombre de la clase en ambos filtery mapes propenso a errores: es fácil olvidarse de cambiar el nombre de la clase en ambos lugares. Una solución mejorada sería algo como esto:

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

Sin embargo, puede haber una penalización de rendimiento al crear un Streampara cada elemento coincidente. Tenga cuidado de usarlo en grandes conjuntos de datos. Aprendí esta solución de @ Tagir Vailev

Andrey
fuente
En este enfoque debes encargarte de NullPointerExceptions, ya select(A.class)que devolverá nullcualquier cosa que no sea un A. Agregar .filter(Objects::nonNull)ayudaría. Por cierto: el enfoque de @Eran es seguro para nulos.
Lars Gendner
Lo siento, mi mal ... JavaDoc de flatMapdice "Si una secuencia asignada es nula, se usa una secuencia vacía". Entonces su solución fue correcta, incluso sin el filtro nulo.
Lars Gendner
3
Aún (en mi opinión) es extraño volver nullcuando podría haber devuelto una transmisión vacía
Alowaniak