简体   繁体   中英

auto binding (type inference) of generic types by the compiler

the following code has compilation error in the line of t3:

public <E> List<E> getList()
{
    return new ArrayList<E>();
}
public <T> void first()
{
    List<T> ret = new ArrayList<T>();
    List<T> list = getList();
    T t1 = ret.get(0);
    T t2 = list.get(0);
    T t3 = getList().get(0);
}

The error message is: Type mismatch: cannot convert from Object to T

I know I can fix the problem using casting or manual binding, my questions is: is it so difficult for the compiler to do auto-binding, is there a case that it will fail?

Edit: added the error message.

Edit: added another example how the error does not occurred.

Edit: removed the second example since it was confusing, made the question more clear.

In the first case you have two generic methods with type parameters named T , but these type parameters are different, so let's assign different names to them:

public <E> List<E> getList() { ... }
public <T> void first() { ... }

Then it works as follows:

  1. An element of List<T> (that is object of type T ) is assigned to the variable of type T , so everything works fine:

      List<T> ret = new ArrayList<T>(); T t1 = ret.get(0); 
  2. Firstly, an object of type List<E> is assigned to List<T> . This statement works fine since type parameter E is inferred from the type of the left side of assignment, so T = E . Then it works as in the previous case:

      List<T> list = getList(); T t2 = list.get(0); 
  3. In this case you are trying to assign object of type E to the variable of type T , but E cannot be inferred and therefore assumed to be Object , so assignment fails:

      T t3 = getList().get(0); 

    You can fix this behaviour by binding E to T manually:

      T t3 = this.<T>getList().get(0); 

In the case of generic class TestGenerics<T> you don't have two independent type parameters, so T in both methods refers to the same type.

You are calling a generics method from generics method. You need to pass the generics argument from the first method to the getList method.

List<T> list = this.<T>getList();

and

T t3 = this.<T>getList().get(0);

From these two the first compiles also without giving the generics argument because the compiler can get the type from the type of list (from the left side of the assignment). In the second case it isn't a direct assignment and so the compiler does not know the type for the getList() call.

These may behave differently with different compilers.

I believe the answer from axtavt to be more correct than my own. Since mine contains a bit of speculation on my part and the other answer explains all the observed behaviour, cites relevant sources and makes general sense I ask you to read axtavts answer instead.

For completeness sake, I will leave my original answer here. Just don't take it for absolute truth.


The signature of java.util.List.get is as follows:

 public abstract java.lang.Object get(int arg0);

get() returns Object, regardless of the parameterization of the type. That is why you cannot assign get(0) to a variable of type T . Even though you can (practically) guarantee that there will always be a T in the list, the interface just doesn't make that promise and the compiler cannot take your word for it.

The problem appears to be with type-erasure. When you compile this code, it will work just fine:

public <T> void someMethod(java.util.List<T> list) {
    T s = list.get(0);
}

The compiler knows it's dealing with a List<T> and can use the signature for List<T> when compiling. It knows that get() will return a T and is perfectly happy. If you change the code to this it no longer works:

public <T> List<T> getList() {
    return new ArrayList<T>();
}

public <T> void someMethod() {
    T s = getList().get(0);
}

The reason for this may be that when compiling the getList() method, the types are erased and it will now return a non-generic java.util.List type. get() on List returns Object and the compiler would no longer be aware that it used to be a List<T> and will complain.

Try casting it to the type T.

    public <T> List<T> getList()
    {
        return new ArrayList<T>();
    }
    public <T> void first()
    {
        List<T> ret = new ArrayList<T>();
        List<T> list = getList();
        T t1 = ret.get(0);
        T t2 = list.get(0);
        T t3 = (T) getList().get(0);
    }

The getList() method seems to be returning a new list each time which is initially empty. get(0) from an empty list will throw an IllegalArgumentException. This is a runtime error though so it may not be causing the compile error.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM