通用返回类型上限 - 接口与类 - 令人惊讶的有效代码 [英] Generic return type upper bound - interface vs. class - surprisingly valid code

查看:23
本文介绍了通用返回类型上限 - 接口与类 - 令人惊讶的有效代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是来自 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 示例,您需要记住泛型既不是协变也不是逆变.这意味着如果 XY 的子类型,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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆