20 votos

Java 8 - cadena llamada al constructor y setter en stream.map()

Tengo una clase

class Foo{
    String name;
    // setter, getter
}

que sólo tiene un constructor predeterminado.

Entonces, estoy tratando de crear una Lista de Foo de la cadena:

Arrays.stream(fooString.split(","))
            .map(name -> {
                Foo x = new Foo();
                x.setName(name);
                return x;

            }).collect(Collectors.toList()));

Ya que no hay ningún constructor que toma un nombre, simplemente no me puedo utilizar un método de referencia. Por supuesto, yo podría extraer de esas tres líneas, con la llamada al constructor y el setter, en un método, pero ¿hay alguna mejor o de manera concisa para hacer eso? (sin cambiar Foo, que es un archivo generado)

16voto

Holger Puntos 13789

Si esto sucede varias veces, usted puede crear un genérico método de utilidad la gestión del problema de la construcción de un objeto dado un valor de la propiedad:

public static <T,V> Function<V,T> create(
    Supplier<? extends T> constructor, BiConsumer<? super T, ? super V> setter) {
    return v -> {
        T t=constructor.get();
        setter.accept(t, v);
        return t;
    };
}

A continuación, se puede utilizar como:

List<Foo> l = Arrays.stream(fooString.split(","))
    .map(create(Foo::new, Foo::setName)).collect(Collectors.toList());

Observe cómo este no es específico para Foo ni setName método de:

List<List<String>> l = Arrays.stream(fooString.split(","))
    .map(create(ArrayList<String>::new, List::add)).collect(Collectors.toList());

Por cierto, si fooString se hace muy grande y/o pueden contener gran cantidad de elementos (después de la división), que podría ser más eficiente el uso de Pattern.compile(",").splitAsStream(fooString) en lugar de Arrays.stream(fooString.split(",")).

9voto

Tunaki Puntos 2663

No, hay una mejor manera.

La única alternativa es, como dijiste en tu pregunta, para crear una fábrica de Foo objetos:

public class FooFactory {
    public static Foo fromName(String name) {
        Foo foo = new Foo();
        foo.setName(name);
        return foo;
    }
}

y como esta:

Arrays.stream(fooString.split(",")).map(FooFactory::fromName).collect(toList());

Si hay un montón de nombres para dividir, puede usar Pattern.compile(",").splitAsStream(fooString) (y el patrón compilado en una constante para evitar la recreación) en vez de Arrays.stream(fooString.split(",")) .

7voto

aleroot Puntos 30853

En este caso no tienes demasiadas alternativas a menos que agregue un constructor que toma el nombre como parámetro, o crear un método estático de fábrica que crear una instancia.

3voto

Jaroslaw Pawlak Puntos 2512

Otra alternativa que no se menciona todavía sería la subclase Foo de la clase, sin embargo esto puede tener algunas desventajas - es difícil decir si sería adecuada solución a su problema, como no conozco el contexto.

public class Bar extends Foo {

    public Bar(String name) {
        super.setName(name);
    }

}

1voto

Philipp Puntos 22441

.map(n -> new Foo() {{ name = n; }} )

Esto utiliza un bloque de inicialización para establecer una variable de instancia.

Hay sin embargo una advertencia: los objetos devueltos no será realmente de tipo Foo , pero de nuevo, anónimas clases que extienden Foo . Cuando usted sigue el principio de sustitución de Liskov esto no debería ser un problema, pero hay algunas situaciones donde puede ser un motivo de preocupación.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X