Reputation: 8200
I have a class
class Foo{
String name;
// setter, getter
}
which just has a default constructor.
Then, I am trying to create a List of Foo
from some string:
Arrays.stream(fooString.split(","))
.map(name -> {
Foo x = new Foo();
x.setName(name);
return x;
}).collect(Collectors.toList()));
Since there is no constructor which takes a name, I can't simply use a method reference. Of course, I could extract those three lines, with the constructor call and the setter, into a method but is there any better or concise way to do that? (without changing Foo
, which is a generated file)
Upvotes: 36
Views: 34306
Reputation: 5578
Another alternative that no one mentioned yet would be to subclass Foo
class, however this may have some disadvantages - it's difficult to say whether it would be appropriate solution to your problem, as I don't know the context.
public class Bar extends Foo {
public Bar(String name) {
super.setName(name);
}
}
Upvotes: 3
Reputation: 69663
.map(n -> new Foo() {{ name = n; }} )
This uses an initialization block to set an instance-variable.
There is however a caveat: The returned objects will not actually be of type Foo
but of new, anonymous classes which extend Foo
. When you follow the Liskov substitution principle this should not be a problem, but there are a few situations where it might be a concern.
Upvotes: 5
Reputation: 298263
If this happens repeatedly, you may create a generic utility method handling the problem of constructing an object given one property value:
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;
};
}
Then you may use it like:
List<Foo> l = Arrays.stream(fooString.split(","))
.map(create(Foo::new, Foo::setName)).collect(Collectors.toList());
Note how this isn’t specific to Foo
nor its setName
method:
List<List<String>> l = Arrays.stream(fooString.split(","))
.map(create(ArrayList<String>::new, List::add)).collect(Collectors.toList());
By the way, if fooString
gets very large and/or may contain lots of elements (after splitting), it might be more efficient to use Pattern.compile(",").splitAsStream(fooString)
instead of Arrays.stream(fooString.split(","))
.
Upvotes: 34
Reputation: 137104
No, there is no better way.
The only alternative is, like you said in your question, to create a factory for Foo
objects:
public class FooFactory {
public static Foo fromName(String name) {
Foo foo = new Foo();
foo.setName(name);
return foo;
}
}
and use it like this:
Arrays.stream(fooString.split(",")).map(FooFactory::fromName).collect(toList());
If there are a lot of names to split, you can use Pattern.compile(",").splitAsStream(fooString)
(and store the compiled pattern in a constant to avoid recreation) instead of Arrays.stream(fooString.split(","))
.
Upvotes: 11
Reputation: 72636
In this case you don't have too many alternatives unless you add a constructor taking the name as parameter, or you create a static factory method that create your instance .
Upvotes: 8