通用返回类型上限 - 接口与类 - 令人惊讶的有效代码 [英] Generic return type upper bound - interface vs. class - surprisingly valid code
问题描述
这是来自 3rd 方库 API 的真实示例,但经过简化.
使用 Oracle JDK 8u72 编译
考虑这两种方法:
X getCharSequence() {返回(X)你好";}<X 扩展字符串>X getString() {返回(X)你好";}
两者都报告了未经检查的强制转换"警告 - 我明白为什么了.让我困惑的是为什么我可以打电话
Integer x = getCharSequence();
它编译了吗?编译器应该知道Integer
没有实现CharSequence
.调用
Integer y = getString();
给出错误(如预期)
<块引用>不兼容类型:推理变量 X 有不兼容的上限 java.lang.Integer,java.lang.String
有人可以解释为什么这种行为被认为是有效的吗?它会有什么用?
客户端不知道这个调用是不安全的——客户端的代码编译时没有警告.为什么编译不警告/发出错误?
另外,它和这个例子有什么不同:
void doCharSequence(List l) {}列表chsL = 新的 ArrayList();doCharSequence(chsL);//编译列表<整数>intL = 新的 ArrayList();doCharSequence(intL);//错误
正如预期的那样,尝试传递 List
会出现错误:
类 generic.GenericTest 中的方法 doCharSequence 不能应用于给定类型;需要:java.util.List找到:java.util.List原因:推理变量 X 的边界不兼容等式约束:java.lang.Integer上限:java.lang.CharSequence
如果是报错,为什么Integer x = getCharSequence();
不是?
CharSequence
是一个 interface
.因此,即使 SomeClass
没有实现 CharSequence
,也完全可以创建一个类
class SubClass extends SomeClass 实现 CharSequence
所以你可以写
SomeClass c = getCharSequence();
因为推断类型X
是交集类型SomeClass &字符序列
.
这在 Integer
的情况下有点奇怪,因为 Integer
是最终的,但 final
在这些规则中没有任何作用.例如你可以写
另一方面,String
不是interface
,因此不可能扩展SomeClass
以获得String
,因为java不支持类的多重继承.
对于 List
示例,您需要记住泛型既不是协变也不是逆变.这意味着如果 X
是 Y
的子类型,List
既不是 List
Integer
没有实现 CharSequence
,你不能在你的 doCharSequence
方法中使用 List
.
你可以,但是可以编译
void foo(List list) {doCharSequence(list);}
如果你有一个返回一个List
的方法,如下所示:
static 列表富()
你可以做到
列表列表 = foo();
同样,这是因为推断的类型是 Integer &CharSequence
这是Integer
的子类型.
当您指定多个边界(例如
)时,会隐式出现交集类型.
有关详细信息,此处是JLS 的一部分,它解释了类型边界的工作原理.您可以包含多个接口,例如
但只有第一个边界可能是非接口.
This is a real-world example from a 3rd party library API, but simplified.
Compiled with Oracle JDK 8u72
Consider these two methods:
<X extends CharSequence> X getCharSequence() {
return (X) "hello";
}
<X extends String> X getString() {
return (X) "hello";
}
Both report an "unchecked cast" warning - I get why. The thing that baffles me is why can I call
Integer x = getCharSequence();
and it compiles? The compiler should know that Integer
does not implement CharSequence
. The call to
Integer y = getString();
gives an error (as expected)
incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String
Can someone explain why would this behaviour be considered valid? How would it be useful?
The client does not know that this call is unsafe - the client's code compiles without warning. Why wouldn't the compile warn about that / issue an error?
Also, how is it different from this example:
<X extends CharSequence> void doCharSequence(List<X> l) {
}
List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles
List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error
Trying to pass List<Integer>
gives an error, as expected:
method doCharSequence in class generic.GenericTest cannot be applied to given types; required: java.util.List<X> found: java.util.List<java.lang.Integer> reason: inference variable X has incompatible bounds equality constraints: java.lang.Integer upper bounds: java.lang.CharSequence
If that is reported as an error, why Integer x = getCharSequence();
isn't?
CharSequence
is an interface
. Therefore even if SomeClass
does not implement CharSequence
it would be perfectly possible to create a class
class SubClass extends SomeClass implements CharSequence
Therefore you can write
SomeClass c = getCharSequence();
because the inferred type X
is the intersection type SomeClass & CharSequence
.
This is a bit odd in the case of Integer
because Integer
is final, but final
doesn't play any role in these rules. For example you can write
<T extends Integer & CharSequence>
On the other hand, String
is not an interface
, so it would be impossible to extend SomeClass
to get a subtype of String
, because java does not support multiple-inheritance for classes.
With the List
example, you need to remember that generics are neither covariant nor contravariant. This means that if X
is a subtype of Y
, List<X>
is neither a subtype nor a supertype of List<Y>
. Since Integer
does not implement CharSequence
, you cannot use List<Integer>
in your doCharSequence
method.
You can, however get this to compile
<T extends Integer & CharSequence> void foo(List<T> list) {
doCharSequence(list);
}
If you have a method that returns a List<T>
like this:
static <T extends CharSequence> List<T> foo()
you can do
List<? extends Integer> list = foo();
Again, this is because the inferred type is Integer & CharSequence
and this is a subtype of Integer
.
Intersection types occur implicitly when you specify multiple bounds (e.g. <T extends SomeClass & CharSequence>
).
For further information, here is the part of the JLS where it explains how type bounds work. You can include multiple interfaces, e.g.
<T extends String & CharSequence & List & Comparator>
but only the first bound may be a non-interface.
这篇关于通用返回类型上限 - 接口与类 - 令人惊讶的有效代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!