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
。
A
模仿 ResultHandler
B
模仿 DefaultResultSetHandler
(这个 class 不是包私有 - 我预计它应该是)
foo()
模仿 handleResultSets
问题
- 我明白为什么这个语句:
List<SomeDummyClass> someDummyClasses = a.foo();
没有给出任何编译时错误。但是在运行时,我认为它应该给出一个ClassCastException
。这里到底发生了什么?
- 当我执行
System.out.println(someDummyClasses.get(0));
时,我得到一个 ClassCastException
。为什么会这样?
- 是否有更好的方法来重写这些以使代码变得不那么脆弱?
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));
看!这里没有错。没有演员表,更不用说 ClassCastException
s.
如果您这样做,会成为 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?
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 那样考虑编译器产生的未经检查的转换警告,而是正确处理它们,您可以从带来的编译检查中尽可能多地获益通过仿制药。
考虑以下代码:
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
(这个 class 不是包私有 - 我预计它应该是)foo()
模仿handleResultSets
问题
- 我明白为什么这个语句:
List<SomeDummyClass> someDummyClasses = a.foo();
没有给出任何编译时错误。但是在运行时,我认为它应该给出一个ClassCastException
。这里到底发生了什么? - 当我执行
System.out.println(someDummyClasses.get(0));
时,我得到一个ClassCastException
。为什么会这样? - 是否有更好的方法来重写这些以使代码变得不那么脆弱?
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));
看!这里没有错。没有演员表,更不用说 ClassCastException
s.
如果您这样做,会成为 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?
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>
, requiredjava.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 那样考虑编译器产生的未经检查的转换警告,而是正确处理它们,您可以从带来的编译检查中尽可能多地获益通过仿制药。