通用返回类型上限 - 接口与类 - 令人惊讶的有效代码

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&lt;Integer&gt; 会出现错误,正如预期的那样:

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(); 不是?

【问题讨论】:

有趣!在 LHS Integer 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 &amp; CharSequence

这在Integer 的情况下有点奇怪,因为Integer 是最终的,但final 在这些规则中没有任何作用。比如你可以写

<T extends Integer & CharSequence>

另一方面,String 不是interface,因此不可能扩展SomeClass 以获得String 的子类型,因为java 不支持类的多重继承。

对于List 示例,您需要记住泛型既不是协变的也不是逆变的。这意味着如果XY 的子类型,则List&lt;X&gt; 既不是List&lt;Y&gt; 的子类型也不是超类型。由于Integer 没有实现CharSequence,因此您不能在doCharSequence 方法中使用List&lt;Integer&gt;

你可以,但是让它编译

<T extends Integer & CharSequence> void foo(List<T> list) 
    doCharSequence(list);
  

如果您有一个返回List&lt;T&gt; 的方法,如下所示:

static <T extends CharSequence> List<T> foo() 

你可以的

List<? extends Integer> list = foo();

同样,这是因为推断的类型是 Integer &amp; CharSequence,这是 Integer 的子类型。

当您指定多个边界(例如&lt;T extends SomeClass &amp; CharSequence&gt;)时,会隐式出现交集类型。

如需更多信息,here 是 JLS 的一部分,它解释了类型边界的工作原理。您可以包含多个接口,例如

<T extends String & CharSequence & List & Comparator>

但只有第一个边界可能是非接口。

【讨论】:

我不知道您可以在通用定义中添加&amp;。 +1 @flkes 你可以放多个,但只有第一个参数可以是非接口。 &lt;T extends String &amp; List &amp; Comparator&gt; 可以,但&lt;T extends String &amp; Integer&gt; 不行,因为Integer 不是接口。 @PaulBoddington 这些方法有一些实际用途。例如,如果该类型实际上并未用于存储的数据。例如Collections.emptyList()Optional.empty()。这些返回通用接口的实现,但不存储任何内容。 没有人说在编译时为final 的类在运行时为final @Federico Peralta Schaffner:这里的重点是,getCharSequence() 方法承诺返回调用者需要的任何X,包括返回扩展Integer 的类型并实现CharSequence,如果调用者需要它,并且在此承诺下,允许将结果分配给Integer 是正确的。是方法getCharSequence() 被破坏了,因为它没有遵守承诺,但这不是编译器的错。【参考方案2】:

在分配X 之前由编译器推断的类型是Integer &amp; CharSequence。这种类型感觉很奇怪,因为Integer 是最终类型,但它是Java 中完全有效的类型。然后将其转换为Integer,完全可以。

Integer &amp; CharSequence 类型只有一个可能的值:null。使用以下实现:

<X extends CharSequence> X getCharSequence() 
    return null;

以下作业将起作用:

Integer x = getCharSequence();

因为有这个可能的值,所以没有理由认为赋值是错误的,即使它显然是无用的。警告会很有用。

真正的问题是 API,而不是调用站点

事实上,我最近在博客上写过这个API design anti pattern。您应该(几乎)永远不要设计一个泛型方法来返回任意类型,因为您可以(几乎)永远不能保证推断的类型将被传递。一个例外是像 Collections.emptyList() 这样的方法,在这种情况下,列表的空性(和泛型类型擦除)是对 &lt;T&gt; 的任何推断都起作用的原因:

public static final <T> List<T> emptyList() 
    return (List<T>) EMPTY_LIST;

【讨论】:

以上是关于通用返回类型上限 - 接口与类 - 令人惊讶的有效代码的主要内容,如果未能解决你的问题,请参考以下文章

postman设置环境变量,字段值经过json转换后数值超过类型上限的解决方法

第 9 章 类

Powershell:将分数转换为整数 - 令人惊讶的舍入行为

通用扩展类 AND 在 Kotlin 中实现接口

实现具有类型约束的通用接口

使用枚举键入接口字段的索引签名?