web-dev-qa-db-fra.com

Java 8 - chaîner l'appel constructeur et le setter dans stream.map ()

J'ai un cours

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

qui a juste un constructeur par défaut.

Ensuite, j'essaie de créer une liste de Foo à partir d'une chaîne:

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

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

Puisqu'il n'y a pas de constructeur qui prend un nom, je ne peux pas simplement utiliser une référence de méthode. Bien sûr, je pourrais extraire ces trois lignes, avec l'appel du constructeur et le setter, dans une méthode, mais y a-t-il un moyen meilleur ou concis de le faire? (sans modifier Foo, qui est un fichier généré)

31
user140547

Si cela se produit à plusieurs reprises, vous pouvez créer une méthode utilitaire générique gérant le problème de la construction d'un objet avec une valeur de propriété donnée:

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;
    };
}

Ensuite, vous pouvez l'utiliser comme:

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

Notez que cela n'est pas spécifique à Foo ni à sa méthode setName:

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

Soit dit en passant, si fooString devient très volumineux et/ou peut contenir beaucoup d'éléments (après le fractionnement), il pourrait être plus efficace d'utiliser Pattern.compile(",").splitAsStream(fooString) au lieu de Arrays.stream(fooString.split(",")) .

31
Holger

Non, il n'y a pas de meilleur moyen.

La seule alternative est, comme vous l'avez dit dans votre question, de créer une fabrique pour les objets Foo:

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

et utilisez-le comme ceci:

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

S'il y a beaucoup de noms à diviser, vous pouvez utiliser Pattern.compile(",").splitAsStream(fooString) (et stocker le modèle compilé dans une constante pour éviter les recréations) au lieu de Arrays.stream(fooString.split(",")).

11
Tunaki

Dans ce cas, vous n'avez pas trop d'alternatives, sauf si vous ajoutez un constructeur prenant le nom en paramètre ou que vous créez un méthode d'usine statique qui crée votre instance.

8
aleroot

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

Cela utilise un bloc d'initialisation pour définir une variable d'instance.

Il y a cependant une mise en garde: les objets retournés ne seront pas réellement de type Foo mais de nouvelles classes anonymes qui étendent Foo. Lorsque vous suivez le principe de substitution de Liskov, cela ne devrait pas être un problème, mais il y a quelques situations où cela pourrait être un problème.

4
Philipp

Une autre alternative que personne n'a encore mentionnée serait de sous-classer la classe Foo, mais cela peut avoir certains inconvénients - il est difficile de dire si ce serait une solution appropriée à votre problème, car je ne connais pas le contexte.

public class Bar extends Foo {

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

}
3
Jaroslaw Pawlak