Home > OS >  Instantiate generic classes with different constructor arguments
Instantiate generic classes with different constructor arguments

Time:04-08

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.)

  • Related