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?

6
  • sorry, this has nothing to have with your question; what is this IDE ? Commented Aug 16, 2016 at 16:48
  • 1
    Generic types are erased at runtime. Commented Aug 16, 2016 at 16:48
  • 3
    stackoverflow.com/questions/339699/… Commented Aug 16, 2016 at 16:48
  • @whyn0t The one and only IntelliJ from JetBrains. Commented Aug 16, 2016 at 16:48
  • (damn my netbeans looks too ugly) thanks Commented Aug 16, 2016 at 16:53

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

how can I make sure from the instantiation code that the correct type of list is being used?
@mattalxndr, You can't make sure that the correct type is being used. There are no such things as List<Integer> or List<String> 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.
@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).
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();

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.