覆盖Java中通用方法的正确方法

时间:2019-07-15 17:25:55

标签: java generics type-erasure raw-types

考虑以下代码:

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

问题

  • 我知道为什么这样的语句:List<SomeDummyClass> someDummyClasses = a.foo();没有给出任何编译时错误。但是在运行时,我认为它应该给出一个ClassCastException。这里到底发生了什么?
  • 当我System.out.println(someDummyClasses.get(0));时,我得到一个ClassCastException。为什么会这样?
  • 是否有更好的方法来重写这些代码,以使代码变得不那么脆弱?

3 个答案:

答案 0 :(得分:2)

  

我很困惑为什么我们可以通过任何编译来覆盖foo()方法   时间错误。

是的,但是您会收到编译警告,因为被覆盖的方法List<Object>中的返回类型比接口方法中的List<E>更具体:

  

未经检查的覆盖:返回类型需要未经检查的转换。找到了   java.util.List<java.lang.Object>,必填java.util.List<E>

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

关于:

  

当我写这篇文章时,这变得特别不直观   列出someDummyClasses = a.foo();这是完美的   有效(无编译时错误)。

为什么会出现编译错误?

您在这里将a声明为A

A a = new B();  

所以在这里:

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

在编译时,foo()指的是接口中声明的<E> List<E> foo()方法,而范围为E且不受限制的通用方法(Object也是如此)是设计为使声明的返回类型适应目标:这里是由方法的客户端声明的变量的类型,以分配方法的返回。

将声明更改为:

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();

这个故事的寓意:不要考虑编译器产生的未经检查的转换警告,例如外观/细节,但要正确处理它们,您可以从泛型带来的编译检查中尽可能多地受益。

答案 1 :(得分:1)

  

但是在运行时,我认为它应该给出ClassCastException。这里实际上发生了什么?

请记住,Java中的泛型纯粹是编译时的事情。编译代码时,编译器将删除所有通用参数,并在必要时添加强制类型转换。您的运行时代码如下:

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));

看!这里没事。没有强制转换,更不用说ClassCastException了。

如果您这样做的话,ClassCastException

SomeDummyClass obj = someDummyClasses.get(0);

因为上面的代码将在运行时变为:

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

当我做System.out.println(someDummyClasses.get(0));我得到一个ClassCastException。为什么会这样?

No it does not

  

是否有更好的方法来重写这些代码,以使代码变得不那么脆弱?

(请注意,我对mybatis不太了解)

我认为您应该在这里将A设为通用接口:

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

并创建B implements A<Object>。这样,至少它更加类型安全。

答案 2 :(得分:0)

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

2nd)由于您将Integers和Strings分配给List,因此您会收到ClassCastException

3rd)您的接口可能像<E extends SummyDummyClass> List<E> foo();那样使B类的声明无效,依此类推,由于您事先知道了E的超类的类型,因此您需要进行的强制转换也较少。