Подтвердить что ты не робот

Java 8 - вызов конструктора цепочки и setter в stream.map()

У меня есть класс

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

который имеет только конструктор по умолчанию.

Затем я пытаюсь создать Список Foo из некоторой строки:

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

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

Поскольку нет конструктора, который принимает имя, я не могу просто использовать ссылку на метод. Конечно, я мог бы извлечь эти три строки, с вызовом конструктора и установщиком, в метод, но есть ли лучший или лаконичный способ сделать это? (без изменения Foo, который является сгенерированным файлом)

4b9b3361

Ответ 1

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

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

Затем вы можете использовать его как:

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

Обратите внимание, что это не относится к Foo и его методу setName:

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

Кстати, если fooString становится очень большим и/или может содержать много элементов (после расщепления), возможно, более эффективно использовать Pattern.compile(",").splitAsStream(fooString) вместо Arrays.stream(fooString.split(",")).

Ответ 2

Нет, нет лучшего способа.

Единственная альтернатива, как вы сказали в своем вопросе, создать объект factory для Foo:

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

и используйте его следующим образом:

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

Если для разделения есть много имен, вы можете использовать Pattern.compile(",").splitAsStream(fooString) (и сохранить скомпилированный шаблон в константе во избежание отдыха) вместо Arrays.stream(fooString.split(",")).

Ответ 3

В этом случае у вас слишком много альтернатив, если вы не добавите конструктор, берущий имя в качестве параметра, или вы создадите метод static factory, который создает ваш экземпляр.

Ответ 4

Еще одна альтернатива, о которой никто еще не упоминал, относится к классу подкласса Foo, однако это может иметь некоторые недостатки - трудно сказать, будет ли это подходящее решение вашей проблемы, поскольку я не знаю контекста.

public class Bar extends Foo {

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

}

Ответ 5

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

Здесь используется блок инициализации для установки переменной экземпляра.

Однако существует оговорка: возвращенные объекты фактически не будут иметь тип Foo, а новых анонимных классов, которые расширяют Foo. Когда вы следуете принципу замещения Лискова, это не должно быть проблемой, но есть несколько ситуаций, когда это может быть проблемой.