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
, который является сгенерированным файлом)
Ответы
Ответ 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
. Когда вы следуете принципу замещения Лискова, это не должно быть проблемой, но есть несколько ситуаций, когда это может быть проблемой.