考虑以下代码:
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
。
A
模仿ResultHandler
B
模仿DefaultResultSetHandler
(此类不是私有包-我本来应该是的)foo()
模仿handleResultSets
List<SomeDummyClass> someDummyClasses = a.foo();
没有给出任何编译时错误。但是在运行时,我认为它应该给出一个ClassCastException
。这里到底发生了什么?System.out.println(someDummyClasses.get(0));
时,我得到一个ClassCastException
。为什么会这样?答案 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
。为什么会这样?
是否有更好的方法来重写这些代码,以使代码变得不那么脆弱?
(请注意,我对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
的超类的类型,因此您需要进行的强制转换也较少。