Java 通用返回类型上限 - 接口与类 - 令人惊讶的有效代码
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36402646/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Generic return type upper bound - interface vs. class - surprisingly valid code
提问by Adam Michalik
This is a real-world example from a 3rd party library API, but simplified.
这是来自 3rd 方库 API 的真实示例,但经过简化。
Compiled with Oracle JDK 8u72
用 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
不实现CharSequence
. 对
Integer y = getString();
gives an error (as expected)
给出错误(如预期)
incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String
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:
List<Integer>
正如预期的那样,尝试通过会出现错误:
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
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?
如果这被报告为错误,为什么Integer x = getCharSequence();
不是?
采纳答案by Paul Boddington
CharSequence
is an interface
. Therefore even if SomeClass
does not implement CharSequence
it would be perfectly possible to create a class
CharSequence
是一个interface
。因此,即使SomeClass
没有实现CharSequence
,也完全有可能创建一个类
class SubClass extends SomeClass implements CharSequence
Therefore you can write
因此你可以写
SomeClass c = getCharSequence();
because the inferred type X
is the intersection type SomeClass & CharSequence
.
因为推断的类型X
是交集类型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
这在Integer
因为Integer
是 final的情况下有点奇怪,但final
在这些规则中没有任何作用。例如你可以写
<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.
另一方面,String
不是interface
,因此无法扩展SomeClass
以获得 的子类型String
,因为 java 不支持类的多重继承。
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.
在这个List
例子中,你需要记住泛型既不是协变也不是逆变。这意味着 ifX
是 的子类型Y
,List<X>
既不是 的子类型,也不是 的超类型List<Y>
。由于Integer
没有实现CharSequence
,你不能List<Integer>
在你的doCharSequence
方法中使用。
You can, however get this to compile
但是你可以编译这个
<T extends Integer & CharSequence> void foo(List<T> list) {
doCharSequence(list);
}
If you have a method that returnsa List<T>
like this:
如果你有一个方法返回一个List<T>
像这样的:
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
.
同样,这是因为推断的类型是Integer & CharSequence
并且这是 的子类型Integer
。
Intersection types occur implicitly when you specify multiple bounds (e.g. <T extends SomeClass & CharSequence>
).
当您指定多个边界(例如<T extends SomeClass & CharSequence>
)时,交叉点类型会隐式出现。
For further information, hereis the part of the JLS where it explains how type bounds work. You can include multiple interfaces, e.g.
欲了解更多信息,这里是它解释了如何类型界定工作JLS的一部分。您可以包含多个接口,例如
<T extends String & CharSequence & List & Comparator>
but only the first bound may be a non-interface.
但只有第一个边界可能是非接口。
回答by Lukas Eder
The type that is inferred by your compiler prior to the assignment for X
is Integer & CharSequence
. This type feelsweird, because Integer
is final, but it's a perfectly valid type in Java. It is then cast to Integer
, which is perfectly OK.
您的编译器在为 赋值之前推断出的类型X
是Integer & CharSequence
。这种类型感觉很奇怪,因为它Integer
是 final,但它在 Java 中是一个完全有效的类型。然后将Integer
其强制转换为,这完全没问题。
There is exactly one possible value for the Integer & CharSequence
type: null
. With the following implementation:
Integer & CharSequence
类型只有一个可能的值:null
。使用以下实现:
<X extends CharSequence> X getCharSequence() {
return null;
}
The following assignment will work:
以下作业将起作用:
Integer x = getCharSequence();
Because of this possible value, there's no reason why the assignment should be wrong, even if it is obviously useless. A warning would be useful.
由于这个可能的值,没有理由为什么赋值应该是错误的,即使它显然没有用。警告会很有用。
The real problem is the API, not the call site
真正的问题是 API,而不是调用站点
In fact, I've recently blogged about this API design anti pattern. You should (almost) never design a generic method to return arbitrary types because you can (almost) never guarantee that the inferred type will be delivered. An exception are methods like Collections.emptyList()
, in case of which the emptiness of the list (and generic type erasure) is the reason why any inference for <T>
will work:
事实上,我最近写了一篇关于这种API 设计反模式的博客。你应该(几乎)永远不要设计一个返回任意类型的泛型方法,因为你(几乎)永远不能保证推断的类型会被传递。一个例外是像这样的方法Collections.emptyList()
,在这种情况下,列表的空性(和泛型类型擦除)是任何推断有效的原因<T>
:
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}