¿Existe una alternativa a la instancia de cuando se filtra un flujo de Java por clase?

8

Tengo una situación inesperada en un proyecto en el que todos los tipos que extienden una clase se empaquetan en una colección Java; pero solo una extensión específica de esa clase contiene un método adicional. Llamémoslo "también ()"; y déjenme aclarar que ninguna otra extensión lo tiene. Justo antes de realizar una tarea en cada elemento de esa colección, necesito llamar también () a cada elemento que lo implemente.

La forma más fácil de avanzar es esta:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .forEach(item -> ((SpecificItem)item).also()));
stuff.stream().forEach(item -> item.method());

Funciona bien, pero no me siento cómodo con la "instancia de" allí. Eso generalmente es un marcador de mal olor del código. Es muy posible que refactorice esta clase solo para deshacerme de ella. Sin embargo, antes de hacer algo tan dramático, pensé en consultar con la comunidad y ver si alguien con más experiencia con Streams o Collections tenía una solución más simple.

Como ejemplo (ciertamente no exclusivo), ¿es posible obtener una vista de una colección que filtre las entradas por clase?

Michael Eric Oberlin
fuente
2
Si necesita filtrar SpecificItem, y esta es la forma correcta de especificar el predicado, entonces, ¿por qué le preocupa tanto instanceofestar allí?
Robert Harvey
¿Los objetos en la secuencia implementan alguna interfaz común que le permita discriminar entre clases? Algunos .getKind()o .isSpecific()?
9000
@ 9000 Desafortunadamente no, y creo que agregar algo así lo desordenaría un poco más.
Michael Eric Oberlin
8
¿Hay alguna razón por la cual SpecificItemla implementación no solo llame also()primero?
Tristan Burnside
1
@ LuísGuilherme Iterableno tiene un stream()método, pero puede llamar forEach()directamente a un Iterable.
shmosel

Respuestas:

3

Te sugiero que .mapllames para hacer el reparto por ti. Entonces su código posterior puede usar la 'cosa real'.

Antes de:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .forEach(item -> ((SpecificItem)item).also()));

Después:

stuff.stream().filter(item -> item instanceof SpecificItem)
            .map(item -> (SpecificItem)item)
            .forEach(specificItem -> specificItem.also()));

Esto no es perfecto, pero parece limpiar un poco las cosas.

Jon Onstott
fuente
Es mi interés eliminar la mayor cantidad de casting posible del código de trabajo, y tiene razón, el mapa está destinado exactamente a eso.
Michael Eric Oberlin
Más funcionalmente: stuff.stream().filter(item -> item instanceof SpecificItem).map(SpecificItem.class::cast).forEach(SpecificItem::also);
strickli
3

Tengo una situación inesperada en un proyecto en el que todos los tipos que extienden una clase se empaquetan en una colección Java; pero solo una extensión de esa clase implementa un método. Llamémoslo "también ()". Justo antes de realizar una tarea en cada elemento de esa colección, necesito llamar también () a cada elemento que lo implemente.

Obviamente es un diseño defectuoso. Por lo que escribió no está claro, lo que significa, que una clase no implementa el método. Si simplemente no hace nada , no importaría, así que supongo que hay un efecto secundario no deseado.

Funciona bien, pero no me siento cómodo con la "instancia de" allí.

Tus agallas son correctas. Un buen diseño orientado a objetos funcionaría sin mayor conocimiento de qué es exactamente un objeto, o si se trata de una instancia de un tipo especial.

Sin embargo, antes de hacer algo tan dramático, pensé en consultar con la comunidad y ver si alguien con más experiencia con Streams o Collections tenía una solución más simple.

Refactorizar no es dramático. Mejora la calidad del código.

Con su base de código dada, la solución más simple sería, para asegurarse, tener dos colecciones separadas, una con la clase principal y otra con la clase secundaria. Pero eso no está del todo limpio.

Thomas Junk
fuente
He editado mi pregunta para que sea más específica. Para aclarar más, este es un trabajo de crack para un cambio que se necesita de inmediato; Hay que tener en cuenta una extensión de la clase. En cuanto a la refactorización, digamos que es una parte esencial de ser un programador. Sin embargo, estoy buscando el refactor menos dramático posible.
Michael Eric Oberlin
Para su pregunta, hay una respuesta clara: si el filtro necesita saber, con qué instancia tiene que tratar, no hay forma de evitarlo (excepto algún tipo de magia de reflexión extraña, que es una forma más críptica de preguntar instancia de ). Pero el tiempo que ahorra ahora para una solución rápida y sucia se gasta más adelante en el ciclo de vida del producto doble en la corrección de errores debido a un mal diseño.
Thomas Junk
@MichaelEricOberlin: Entonces, si escucho a Thomas correctamente, sus elecciones parecen ser: cambiar el diseño o dejarlo como está.
Robert Harvey
@RobertHarvey lo expresó de esta manera, suena tautológico;) si no quiere refactorizar, no queda otra opción que seguir el camino actual.
Thomas Junk
2

Es difícil dar recomendaciones específicas sin saber qué son SpecificItemy qué also()son en realidad, pero:

Definir also()en la superclase de SpecificItem. Dale una implementación predeterminada que no haga nada (dale un cuerpo de método vacío). Las subclases individuales pueden anularlo si lo desea. Dependiendo de lo que alsorealmente es, es posible que deba cambiarle el nombre a algo que tenga sentido para todas las clases involucradas.

Alex D
fuente
Eso es bastante sensato, y puedo terminar refactorizando para hacer eso.
Michael Eric Oberlin
1

una alternativa:

  1. hacer que SpecificItem implemente una interfaz (diga "Filtrable")
  2. hacer una nueva clase que extienda Stream
  3. cree un nuevo método de 'filtro' que acepte objetos que implementen su interfaz
  4. anular el método de transmisión original y redirigirlo a su implementación (enviando el parámetro predicado a su interfaz)

de esa manera solo los objetos que implementan su interfaz podrían pasar temas a su método ... no es necesario usar instanceof

public class MyStream extends Stream
{
    //...

    Stream<Filterable> filter(Predicate<? implements Filterable> predicate)
    {
         return super.filter(predicate);
    }
}
ymz
fuente
3
Esa no es la forma: mejorar el mal diseño al empeorarlo.
Thomas Junk
Tengo que estar de acuerdo con Thomas Junk aquí. El paso tres básicamente hace lo que ya estoy haciendo; y sin volver a usar instanceof, no veo cómo su método resolvería esto.
Michael Eric Oberlin
1
@MichaelEricOberlin: Esto es exactamente lo que me preocupaba; crear un Rube Goldberg solo para satisfacer la idea de alguien de una "mejor práctica".
Robert Harvey
1

Espero que no me falte nada obvio aquí (también como lo sugiere el comentario de @Tristan Burnside ), pero ¿por qué no puedo SpecificItem.method()llamar also()primero?

public class SpecificItem extends Item {
    ...
    public void method() {
        also();
        super.method();
    }
}

Como ejemplo (ciertamente no exclusivo), ¿es posible obtener una vista de una colección que filtre las entradas por clase?

Una forma fluida en la que puedo pensar, a expensas de tal vez algún impacto en el rendimiento (YMMV), es a collect() través Collectors.groupingBy()de la clase como la clave, y luego elegir lo que desea del resultado Map. Los valores se almacenan como a List, por lo que si esperaba hacer un filtrado de este tipo en Sety espera obtener un filtro Set, entonces necesitará un paso adicional para poner los valores en un resultado Settambién.

hjk
fuente
-1

Aquí está mi enfoque:

stuff.stream().filter(item -> SpecificItem.class.isInstance(item)).map(item  -> SpecificItem.class.cast(item)).forEach(item -> item.also());
stuff.stream().forEach(item -> item.method());

Sin instancia de , no hay conversiones explícitas (en realidad es una conversión explícita, pero enmascarada por una llamada al método). Creo que esto es tan bueno como puede ser.

beto
fuente
1
¿No es esta una versión menos legible del original?
grahamparks
Bueno, en realidad hace lo mismo de una manera segura. Puede ser más simple según el contexto del código. Lo uso para filtrar ciertos tipos dentro de una lista genérica, a excepción de la parte forEach.
beto