Assigning List of Integer Into a List of String

2019-04-03 01:45发布

问题:

I was learning Generics in Java and I came close to a very interesting piece of code. I know in Java it is illegal to add list of one type to another.

List<Integer> integerList = new ArrayList<Integer>();
List<String> stringList=integerList;

So in the second line I get a compile time error.
But if I create a generic method inside a class like this,

class  GenericClass <E>{
    void genericFunction(List<String> stringList) {
        stringList.add("foo");
    }
    // some other code
}

And in the main class call the method with list of Integer I am not getting any error.

public class Main {
  public static void main(String args[]) {

     GenericClass genericClass=new GenericClass();
     List<Integer> integerList= new ArrayList<Integer>();
     integerList.add(100);
     genericClass.genericFunction(integerList);
     System.out.println(integerList.get(0));
     System.out.println(integerList.get(1));
  }
}

Output
100
foo

Why I am not getting any error?

回答1:

You have mixed Generic with raw type. It will compile fine but at run-time it might fail because Generic information is lost at run-time.

Generic should be used to track such bugs at compile-time.

It's better explained at What is a raw type and why shouldn't we use it? in detail.


Warning: Type safety: The method genericFunction(List) belongs to the raw type GenericClass. References to generic type GenericClass<E> should be parameterized.

If you have two methods with same name with different Generic type of List then it results into compile time error. The compiler is unable to resolve Generic type in case of method arguments that can be proved by below sample code.

Sample code: (compiler error - not a valid overloaded method)

void genericFunction(List<String> stringList){...}
void genericFunction(List<Integer> stringList){...}

Make some changes and try it again:

class  GenericClass <E>{
    void genericFunction(List<E> stringList) {
        ...
    }
    // some other code
}

...

GenericClass<String> genericClass=new GenericClass<String>(); // Genreric object
List<Integer> integerList= new ArrayList<Integer>();
integerList.add(100);
genericClass.genericFunction(integerList); // compile time error

Create methods in such a way

class GenericClass<E> {
    private List<E> list = new ArrayList<E>();

    public void addAll(List<E> newList) {
        list.addAll(newList);
    }

    public void add(E e) {
        list.add(e);
    }

    public E get(int index) {
        return list.get(index);
    }
    // some other code
}


回答2:

You are not getting any compile time error because by using GenericClass<E> in a raw way :

GenericClass genericClass = new GenericClass();,

you are practically telling the compiler to disable generic type checkings because you don't care.

So the :

void genericFunction(List<String> stringList)

becomes

void genericFunction(List stringList) for the compiler.

You can try the following : GenericClass<?> genericClass , and you'll notice immediately that the compiler becomes aware of the generics bad use, and it will show you the error :

The method genericFunction(List<String>) in the type GenericClass<capture#1-of ?> is not applicable for the arguments (List<Integer>)

Also, if you try to get the class of the 2nd position object at runtime:

System.out.println(integerList.get(1).getClass());

, you'll get an error: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer.



回答3:

This happens, because (quite surprisingly for me) you have turned off generic type checking for the whole GenericClass class.

You need to be aware that you are, first of all, constructing a generic class without type argument here:

GenericClass genericClass = new GenericClass();

And appereantly, because of this, your following code:

class GenericClass<E> {
    void genericFunction(List<String> stringList) {
        stringList.add("foo");
    }
    // some other code
}

got refined to:

class GenericClass {
    void genericFunction(List stringList) {
        stringList.add("foo");
    }
    // some other code
}

Note that List also became a raw type, which is rather surprising to me.

You can find the full answer here, referencing the JLS: https://stackoverflow.com/a/662257/2057294 as explained by Jon Skeet.

I think it is fair that this happens (though not what you would expect), because if you decide to use raw types, it is assumed that you are using Java 4 or lower and have no access at all to generics either way, so it may as well not provide them for methods not involving the generic type from the class that got erased.



回答4:

To add to the other answers.

Indeed you are mixing a raw (unparameterized) type with parameterized types which causes a type loss and an apparently correct argument passing to genericFunction when type safety checks should not allow you to.

The question remains on why the List<String> type is lost in an unparameterized GenericClass object. The reason the compiler "disables" type checking in your case is the type erasure mechanism, which says (in simple terms) your raw object belongs to a class with the following contents:

class  GenericClass {
    void genericFunction(List stringList) {
        stringList.add("foo");
    }
    // some other code
}

Which as you see does not make any type checks whatsoever on the contents of the list being passed (it erases type parameters everywhere). Even more, type erasure also erases your parameterized type from the integerList variable, which makes it completely suitable as an argument for your genericFunction method.

Therefore, as others pointed out, it's always best to help Java maintain its safe type-checking mechanisms intact:

     GenericClass<?> genericClass=new GenericClass<Integer>();
     List<Integer> integerList= new ArrayList<Integer>();
     integerList.add(100);
     // no-no here
     genericClass.genericFunction(integerList);
     System.out.println(integerList.get(0));
     System.out.println(integerList.get(1));

But then again if you do, the compiler does its job and goes bazooka on you.

Read more on type erasure here. It's a nice feature that allows generic type checking while still keeping backward compatibility to pre-generics Java versions.



回答5:

class  GenericClass <E>{
    void genericFunction(List<String> stringList) {
        stringList.add("foo");
    }
    // some other code
}

when you write just after the class name.It specifies the type parameters.Its a generic functionality.This introduces the type variable, E, that can be used anywhere inside the class. A type variable can be any non-primitive type you specify: any class type, any interface type, any array type, or even another type variable.