6

I have this class:

public class TestSubject {
    public TestSubject(List<Integer> list) {
    }
}

I'm instantiating it like this and somehow it's working, even though I'm inserting an ArrayList<String> into a constructor that accepts List<Integer>:

List<String> strings = new ArrayList<>();
strings.add("foo");
Constructor<TestSubject> constructor = TestSubject.class.getConstructor(List.class);
TestSubject test = constructor.newInstance(strings);

This is what I see after instantiation:

enter image description here

How can this be possible?

Also, how can I make sure from the instantiation code that the correct type of list is being used?

mattalxndr
  • 9,143
  • 8
  • 56
  • 87

2 Answers2

3

This is happening due to type erasure. As <Integer> will be erased and it will be List only. However you are creating the instance with reflection and at runtime it will not check the type of the List.

Here type of the list is checked during compile time if you create it with new but in this case you skipped the compile time check and during runtime it is valid because of type erasure.

In your case there is no direct way other than checking the type of elements manually in the constructor.

akash
  • 22,664
  • 11
  • 59
  • 87
  • how can I make sure from the instantiation code that the correct type of list is being used? – mattalxndr Aug 16 '16 at 16:51
  • @mattalxndr http://stackoverflow.com/questions/3403909/get-generic-type-of-class-at-runtime – Jean-François Savard Aug 16 '16 at 16:53
  • 5
    @mattalxndr, You _can't_ make sure that the correct type is being used. There are no such things as `List` or `List` at run-time: There is only `List`. That's what "type erasure" means. The best you can do is to examine the individual _members_ of the list to find out whether they are `Integer` or `String`, but be aware that a single list could, in principle, contain objects of both types. – Solomon Slow Aug 16 '16 at 16:56
  • 1
    @mattalxndr *"how can I make sure from the instantiation code that the correct type of list is being used?"* By not using reflection. Java enforces generic type safety at *compile-time*, and will warn you when you do stuff it cannot enforce (so-called "unsafe" warnings). – Andreas Aug 16 '16 at 17:10
0

Answering to a last part of question. Actually you may check if there is a correct type passed. You can obtain generic type parameter for constructor argument like this

// in your case that will give you parametrized type
// java.util.List<java.lang.Integer>
Type type = constructor.getGenericParameterTypes()[0];

ParameterizedType argumentType = (ParameterizedType) type;
// that will give you List type parameter - java.lang.Integer
Type argumentType = type[0];

That also works for fields unless list parameter not generic itself.

There is another trick. You can store generic parameter using type reference using anonymous class:

public abstract class TypeReference<T> {
    private final Type type;

    public TypeReference() {
        if (!getClass().isAnonymousClass()) {
            throw new IllegalArgumentException(getClass() + " should be anonymous");
        }

        final Type superClass = getClass().getGenericSuperclass();
        if (!(superClass instanceof ParameterizedType)) {
            throw new IllegalArgumentException("missing type parameter due to type erasure");
        }

        this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }

    public final Type getType() {
        return this.type;
    }
}

So here is very basic idea how you can achieve your goal. You may preserve generic parameter using type reference and check argument like this:

public class ObjectBuilder<T> {
    List<Object> validatedArguments = new ArrayList<>();            
    Constructor<T> ctor = /*... */;


    public void <A> addArgument(A argument
                                TypeReference<A> argumentType) {
         int currentArgument = validatedArguments.size();
         Type ctorArgumentType = 
             ctor.getGenericParameterTypes()[currentArgument]/* */;
         Type argumentType = argumentType.getType();
         // compare it carefully!

         validatedArguments.add(argument);
    }

    public T build() {
       // new instance creating ...
    }
}

ObjectBuilder<TestSubject> subject = new ObjectBuilder<>();
subject.addArgument(list, new TypeReference<List<Integer>>() {})
TestSubject obj = subject.build();
vsminkov
  • 10,912
  • 2
  • 38
  • 50