I have two similar classes, Foo and Bar
public class Foo{
private String name;
public Foo(String name){
this.name = name;
}
}
public class Bar{
private String name;
public Bar(String name){
this.name = name;
}
}
And I've got two methods in another class that creates a Set of Foo (1st method) and Bar (2nd method) and are pretty much the same. The first one:
private Set<Foo> buildFoos(final List<String> names)
{
final Set<Foo> set = new HashSet<>();
for (int i = 0; i < names.size(); i )
{
set.add(new Foo(names.get(i)));
}
return set;
}
And the second one:
private Set<Bar> buildBars(final List<String> names)
{
final Set<Bar> set = new HashSet<>();
for (int i = 0; i < names.size(); i )
{
set.add(new Bar(names.get(i)));
}
return set;
}
As you can see, both methods are pretty much the same so I thought I could use generics to use only one method. My approach would be something like this:
private <T> Set<T> build(final Class<T> clazz, final List<String> names)
{
final Set<T> set = new HashSet<>();
for (int i = 0; i < names.size(); i )
{
set.add(clazz.getConstructor(String.class).newInstance(names.get(i)));
}
return set;
}
I've tested this and it's supposed to be working but my question is, what would happen if Foo or Bar would have a different constructor (so for instance, Bar would have another String as the 2nd parameter). All I can think about is to check the instance of the class and pick one of the two constructors (something like this)
private <T> Set<T> build(final Class<T> clazz, final List<String> names)
{
final Set<T> set = new HashSet<>();
for (int i = 0; i < names.size(); i )
{
if(clazz.isInstance(Foo.class){
set.add(clazz.getConstructor(String.class).newInstance(names.get(i)));
}
else if(clazz.isInstance(Bar.class){
set.add(clazz.getConstructor(String.class, String.class).newInstance(names.get(i), "example"));
}
}
return set;
}
Is there a better way of achieving this? Is this even a good practice? Any tip is always appreciated. Thanks in advance!
CodePudding user response:
It seems to me that it would be better to take a Function<String, T>
instead of a Class<T>
. That way you don't need reflection, and callers can pass in constructors, factory methods, lambdas that use multi-parameter constructors, whatever they want. So your method would be:
private <T> Set<T> build(final Function<String, T> factory, final List<String> names) {
final Set<T> set = new HashSet<>();
for (String name : names) {
set.add(factory.apply(name));
}
return set;
}
I suspect this could be written more simply using streaming, but that's a separate issue. (I did take the opportunity to use an enhanced-for loop for simplicity though.)