通用返回类型上限 - 接口与类 - 令人惊讶的有效代码
Posted
技术标签:
【中文标题】通用返回类型上限 - 接口与类 - 令人惊讶的有效代码【英文标题】:Generic return type upper bound - interface vs. class - surprisingly valid code 【发布时间】:2016-07-23 23:51:42 【问题描述】:这是一个来自 3rd 方库 API 的真实示例,但经过了简化。
使用 Oracle JDK 8u72 编译
考虑这两种方法:
<X extends CharSequence> X getCharSequence()
return (X) "hello";
<X extends String> X getString()
return (X) "hello";
两者都报告“未经检查的演员表”警告 - 我明白了。让我困惑的是为什么我可以打电话
Integer x = getCharSequence();
它编译了吗?编译器应该知道Integer
没有实现CharSequence
。呼吁
Integer y = getString();
给出错误(如预期)
incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String
有人可以解释为什么这种行为会被认为是有效的吗?它有什么用处?
客户端不知道这个调用是不安全的——客户端的代码在没有警告的情况下编译。为什么编译器不会对此发出警告/发出错误?
另外,它和这个例子有什么不同:
<X extends CharSequence> void doCharSequence(List<X> l)
List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles
List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error
尝试传递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
如果这被报告为错误,为什么Integer x = getCharSequence();
不是?
【问题讨论】:
有趣!在 LHSInteger x = getCharSequence();
上强制转换将编译,但在 RHS Integer x = (Integer) getCharSequence();
上强制转换编译失败
你使用的是什么版本的java编译器?请在问题中指定此信息。
@FedericoPeraltaSchaffner 不明白为什么这很重要 - 这是一个直接关于 JLS 的问题。
@BoristheSpider 因为java8的类型推断机制已经改变
@FedericoPeraltaSchaffner - 我已经用 [java-8] 标记了这个问题,但我现在在帖子中添加了编译器版本。
【参考方案1】:
CharSequence
是一个interface
。因此,即使SomeClass
没有实现CharSequence
,也完全可以创建一个类
class SubClass extends SomeClass implements CharSequence
所以你可以写
SomeClass c = getCharSequence();
因为推断类型X
是交集类型SomeClass & CharSequence
。
这在Integer
的情况下有点奇怪,因为Integer
是最终的,但final
在这些规则中没有任何作用。比如你可以写
<T extends Integer & CharSequence>
另一方面,String
不是interface
,因此不可能扩展SomeClass
以获得String
的子类型,因为java 不支持类的多重继承。
对于List
示例,您需要记住泛型既不是协变的也不是逆变的。这意味着如果X
是Y
的子类型,则List<X>
既不是List<Y>
的子类型也不是超类型。由于Integer
没有实现CharSequence
,因此您不能在doCharSequence
方法中使用List<Integer>
。
你可以,但是让它编译
<T extends Integer & CharSequence> void foo(List<T> list)
doCharSequence(list);
如果您有一个返回List<T>
的方法,如下所示:
static <T extends CharSequence> List<T> foo()
你可以的
List<? extends Integer> list = foo();
同样,这是因为推断的类型是 Integer & CharSequence
,这是 Integer
的子类型。
当您指定多个边界(例如<T extends SomeClass & CharSequence>
)时,会隐式出现交集类型。
如需更多信息,here 是 JLS 的一部分,它解释了类型边界的工作原理。您可以包含多个接口,例如
<T extends String & CharSequence & List & Comparator>
但只有第一个边界可能是非接口。
【讨论】:
我不知道您可以在通用定义中添加&
。 +1
@flkes 你可以放多个,但只有第一个参数可以是非接口。 <T extends String & List & Comparator>
可以,但<T extends String & Integer>
不行,因为Integer
不是接口。
@PaulBoddington 这些方法有一些实际用途。例如,如果该类型实际上并未用于存储的数据。例如Collections.emptyList()
和Optional.empty()
。这些返回通用接口的实现,但不存储任何内容。
没有人说在编译时为final
的类在运行时为final
。
@Federico Peralta Schaffner:这里的重点是,getCharSequence()
方法承诺返回调用者需要的任何X
,包括返回扩展Integer
的类型并实现CharSequence
,如果调用者需要它,并且在此承诺下,允许将结果分配给Integer
是正确的。是方法getCharSequence()
被破坏了,因为它没有遵守承诺,但这不是编译器的错。【参考方案2】:
在分配X
之前由编译器推断的类型是Integer & CharSequence
。这种类型感觉很奇怪,因为Integer
是最终类型,但它是Java 中完全有效的类型。然后将其转换为Integer
,完全可以。
Integer & CharSequence
类型只有一个可能的值:null
。使用以下实现:
<X extends CharSequence> X getCharSequence()
return null;
以下作业将起作用:
Integer x = getCharSequence();
因为有这个可能的值,所以没有理由认为赋值是错误的,即使它显然是无用的。警告会很有用。
真正的问题是 API,而不是调用站点
事实上,我最近在博客上写过这个API design anti pattern。您应该(几乎)永远不要设计一个泛型方法来返回任意类型,因为您可以(几乎)永远不能保证推断的类型将被传递。一个例外是像 Collections.emptyList()
这样的方法,在这种情况下,列表的空性(和泛型类型擦除)是对 <T>
的任何推断都起作用的原因:
public static final <T> List<T> emptyList()
return (List<T>) EMPTY_LIST;
【讨论】:
以上是关于通用返回类型上限 - 接口与类 - 令人惊讶的有效代码的主要内容,如果未能解决你的问题,请参考以下文章
postman设置环境变量,字段值经过json转换后数值超过类型上限的解决方法