Java 中重写泛型方法的正确方法

Proper way of overriding generic methods in Java

考虑以下代码:

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
    }
}

我很困惑为什么我们可以重写 foo() 方法而不会出现任何编译时错误。当我写 List<SomeDummyClass> someDummyClasses = a.foo(); 时,这变得不那么直观了,这是完全有效的(没有编译时错误)。

这种情况的实时示例是 mybatis-3

问题

1st) A#foo 的签名是 return a <E> List<E> 它将 return 一个在赋值时被捕获为 E 的类型列表,List<X> = new A().foo() (如果 A 是一个具体的 class) 编译,因为它捕获类型,而 List<X> = new B().foo() return a List<Object> 并且不能被分配。

2nd) 你得到了一个 ClassCastException,因为你将整数和字符串分配给了一个列表

3rd) 你的接口可以像 <E extends SummyDummyClass> List<E> foo(); 把 B 上的声明 class 变成无效等等,你也需要更少的转换,因为你事先知道 superclass 共 E

But at runtime, I thought it should have given a ClassCastException. What's actually happening here?

请记住 Java 中的泛型纯粹是 compile-time 的东西。编译代码时,编译器会删除所有通用参数,并在必要时添加强制转换。您的运行时代码如下所示:

interface A {
    List foo();
}

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

// ...

List someDummyClasses = a.foo();

System.out.println(someDummyClasses.get(0));

看!这里没有错。没有演员表,更不用说 ClassCastExceptions.

如果您这样做,成为 ClassCastException:

SomeDummyClass obj = someDummyClasses.get(0);

因为上面的代码在运行时会变成这样:

SomeDummyClass obj = (SomeDummyClass)someDummyClasses.get(0);

When I do System.out.println(someDummyClasses.get(0)); I get a ClassCastException. Why is this so?

No it does not.

Is there a better way to re-write these so that the code becomes less fragile?

(注意我对mybatis了解不多)

我认为你应该在这里做的是使 A 成为一个通用接口:

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

并使B implements A<Object>。这样一来,至少多了type-safe.

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

是的,但是你有一个编译警告,因为覆盖方法 List<Object> 中的 return 类型比接口方法中的 List<E> 更具体:

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

不处理这种警告可能会导致泛型的使用不一致。你有一个很好的例子。

关于:

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

为什么会出现编译错误?

在这里你将 a 声明为 A :

A a = new B();  

所以在这里:

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

在编译时,foo() 指的是在接口中声明的 <E> List<E> foo() 方法和一个范围为 E 的泛型方法,它不受限制(Object ) 旨在使声明的 return 类型适应目标:这里是方法的客户端声明的变量类型,用于分配方法 return。

将您的声明更改为:

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

而且连编译都过不了

请注意,您的问题并非特定于覆盖方法。 声明一个方法(它也会产生关于未经检查的转换的警告):

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

而且您可能会以同样不一致的方式使用它:

 List<String> listOfAny = getListOfAny();

这个故事的寓意:不要像 cosmetics/details 那样考虑编译器产生的未经检查的转换警告,而是正确处理它们,您可以从带来的编译检查中尽可能多地获益通过仿制药。