Proper way of overriding generic methods in Java

Lavish Kothari :

Consider the following code:

import java.util.Arrays;
import java.util.List;

class Person {}
class OtherPerson {}
class SomeDummyClass {}

interface A {
    <E>List<E> foo();
}

class B implements A {
    @Override
    public List<Object> foo() {
        return Arrays.asList(Integer.valueOf(3), new Person(), new OtherPerson());
    }
}

public class Main {
    public static void main(String[] args) {
        A a = new B();
        List<SomeDummyClass> someDummyClasses = a.foo();

        System.out.println(someDummyClasses.get(0)); // prints 3
    }
}

I'm confused why we can override the foo() method without any compile time errors. This particularly became less intuitive to reason about when I wrote List<SomeDummyClass> someDummyClasses = a.foo(); and this is perfectly valid (no compile time error).

A real time example of this case is with mybatis-3.

Questions

  • I understand that why is this statement: List<SomeDummyClass> someDummyClasses = a.foo(); not giving any compile time error. But at runtime, I thought it should have given a ClassCastException. What's actuall happening here?
  • When I do System.out.println(someDummyClasses.get(0)); I get a ClassCastException. Why is this so?
  • Is there a better way to re-write these so that the code becomes less fragile?
davidxxx :

I'm confused why we can override the foo() method with any compile time errros.

Yes but you have a compilation warning since the return type in the overrided method List<Object> is more specific that List<E> in the interface method :

Unchecked overriding: return type requires unchecked conversion. Found java.util.List<java.lang.Object>, required java.util.List<E>.

To not handle this kind of warning may lead to an inconsistent use of the generics. And there you have a very good example.

About :

This particularly became less intuitive to reason about when I wrote List someDummyClasses = a.foo(); and this is perfectly valid (no compile time error).

And why would you get a compilation error ?

Here you declare a as a A :

A a = new B();  

So here :

List<SomeDummyClass> someDummyClasses = a.foo();

at compile time, foo() refers to the <E> List<E> foo() method declared in the interface and a method scoped generic with E that is not bounded (so is Object) is designed to adapt the declared return type to the target : here that is the type of the variable declared by the client of the method to assign the method return.

Change your declaration to :

B b = new B();  
List<SomeDummyClass> someDummyClasses = b.foo();

And the compilation would not even pass.

Note that your issue is not specific to overriding method. Declare a method (that also produces a warning about unchecked cast) :

public static <T> List<T> getListOfAny(){
    return (List<T>) new ArrayList<Integer>();
}

And you could use it in the same inconsistent way :

 List<String> listOfAny = getListOfAny();

The moral of this story : don't consider unchecked conversions warnings produced by the compiler like cosmetics/details but handle them correctly and you could benefit as much as possible from the compilation check brought by generics.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=144365&siteId=1