Xirema
Xirema

Reputation: 20396

How to properly query for a Constructor with an argument that is generic

I need to acquire the Constructor of a class at runtime using reflection instead of direct access. For simple types, this is trivial:

public class MyType {
    public MyType(String a, Integer b, Long c) {}
}

Constructor constructor = MyType.class.getConstructor(String.class, Integer.class, Long.class);

But if the class uses generics as part of its arguments, it's not clear what I should put:

public class MyType {
    public MyType(Set<String> a, Integer b, List<Long> c) {}
}

Constructor constructor = MyType.class.getConstructor(Set<String>.class /*Doesn't compile*/, Integer.class, List<Long>.class /*doesn't compile*/);

I can write something like MyType.class.getConstructor(Set.class, Integer.class, List.class); but it's not clear that that will achieve the behavior I want. What's the correct way to write this kind of code?

Upvotes: 0

Views: 63

Answers (2)

C-Otto
C-Otto

Reputation: 5843

The generic information is not part of the information that is used by the getConstructor method. The call you denoted (Set.class, ...) is correct, as it resembles the information available at runtime.

As noted by Andy Turner, you can only have one constructor with this type - even if you try to use different generic arguments for a second constructor in your source code.

If you're curious, you can have a look at the bytecode using javap:

$ javap -private -c MyType
Compiled from "MyType.java"
public class MyType {
  public MyType(java.lang.String, java.lang.Integer, java.lang.Long);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
}

Upvotes: 1

jeojavi
jeojavi

Reputation: 886

There is a way to acquire the constructor you need using reflections, but in this case is more complicated that it seems. You must check all the constructors of the class and all their parameters. Something like this:

public Constructor getConstructor() {
    Constructor constructor = null;
    Constructor<?>[] constructors = MyType.class.getConstructors();
    for(Constructor constructor1 : constructors) {
        Type[] types = constructor1.getGenericParameterTypes();
        if(types.length == 3) {
            if(types[0] instanceof ParameterizedType
                    && types[1].equals(Integer.class)
                    && types[2] instanceof ParameterizedType) {
                ParameterizedType type0 = (ParameterizedType) types[0];
                ParameterizedType type2 = (ParameterizedType) types[2];
                if(type0.getActualTypeArguments().length == 1 
                        && type0.getRawType().equals(Set.class)
                        && type0.getActualTypeArguments()[0].equals(String.class)
                        && type2.getActualTypeArguments().length == 1
                        && type2.getRawType().equals(List.class)
                        && type2.getActualTypeArguments()[0].equals(Long.class)) {
                    return constructor;
                }
            }
        }
    }
    return null;
}

However, as noted by Andy Turner in the comments, you cannot create another constructor with the parameters with the same erasure (Set.class, Integer.class, List.class).

Upvotes: 0

Related Questions