当前位置: 首页 > 面试题库 >

为什么带有绑定的泛型方法可以返回任何类型?

易修洁
2023-03-14
问题内容

为什么以下代码会编译?该方法IElement.getX(String)返回该类型IElement或其子类的实例。类中的代码Main调用该getX(String)方法。编译器允许将返回值存储到类型的变量Integer(显然不在的层次结构中IElement)。

public interface IElement extends CharSequence {
  <T extends IElement> T getX(String value);
}

public class Main {
  public void example(IElement element) {
    Integer x = element.getX("x");
  }
}

IElement 即使在擦除类型之后, 返回类型也不应该仍然是的实例 吗?

getX(String)方法的字节码为:

public abstract <T extends IElement> T getX(java.lang.String);
flags: ACC_PUBLIC, ACC_ABSTRACT
Signature: #7                           // <T::LIElement;>(Ljava/lang/String;)TT;

编辑:String一致地替换为Integer


问题答案:

这实际上是合法的类型推断*。

我们可以将其简化为以下示例(Ideone):

interface Foo {
    <F extends Foo> F bar();

    public static void main(String[] args) {
        Foo foo = null;
        String baz = foo.bar();
    }
}

String & Foo因为Foo是接口,所以允许编译器推断(无意义的,实际上是)交集类型。对于问题中的示例,Integer & IElement可以推断。

这是荒谬的,因为转换是不可能的。我们自己不能做这样的演员:

// won't compile because Integer is final
Integer x = (Integer & IElement) element;

类型推断基本上适用于:

  • 每个方法的类型参数的一组 推断变量
  • 一组必须遵守的 界限
  • 有时 约束 ,其被 降低 至界限。

在算法结束时,将根据绑定集将每个变量 解析 为交集类型,如果有效,则将编译调用。

该过程从8.1.3开始:

当推理开始时,通常从类型参数声明和关联的推理变量的列表中生成绑定集。这样的绑定集构造如下。对于每个 l(1≤l≤p)P1, ..., Pp``α1, ..., αp __

  • […]

  • 否则,对于在
    TypeBound
    中以T分隔的每种类型,界限将出现在集合[…]中。&
    __
    α l <: T[P1:=α1, ..., Pp:=αp]

因此,这意味着首先编译器以F <: FooF的子类型为Foo)的边界开始。

转到18.5.2,将考虑返回目标类型:

如果调用是多边形表达式,则[…] R设为的返回类型mT设为调用的目标类型,然后:

  • […]

  • 否则,约束公式将‹R θ → T›被简化并与[绑定集]合并。

约束公式‹R θ → T›被简化为的另一个边界R θ <: T,因此我们有F <: String

稍后根据18.4解决:

[…] 为每个定义一个候选实例:Ti``αi

  • 否则,在适当的上限处,。αi``U1, ..., Uk``Ti = glb(U1, ..., Uk)

边界与当前边界集合并。α1 = T1, ..., αn = Tn

回想一下我们的范围是F <: Foo, F <: Stringglb(String, Foo)定义为String & Foo。这显然是glb的合法类型,仅要求:

如果对于任何两个 不是interfaces )和,都不是的子类,反之亦然,则是编译时错误。Vi``Vj``Vi``Vj

最后:

如果解析通过实例化实例变量成功完成,则将其替换。然后:T1, ..., Tp``α1, ..., αp``θ'``[P1:=T1, ..., Pp:=Tp]

  • 如果不加以控制的转换是不必要的方法可应用于,然后的调用类型m是通过将获得θ'到的类型m

因此,String & Foo以的类型调用该方法F。我们当然可以将其分配给a String,因此不可能将a转换Foo为a String

String/ Integer是最后一堂课的事实显然未被考虑。

*注意:类型 清除 与问题完全无关。

另外,尽管这也可以在Java 7上编译,但是我认为可以不必担心那里的规范是合理的。Java 7的类型推断本质上是Java
8的较不复杂的版本。出于类似原因进行编译。

作为附录,虽然很奇怪,但这可能永远不会引起尚未出现的问题。编写其返回类型仅从返回目标推断出的泛型方法几乎是没有用的,因为只能null从此类方法返回而无需强制转换。

例如,假设我们有一些映射类似物,它存储特定接口的子类型:

interface FooImplMap {
    void put(String key, Foo value);
    <F extends Foo> F get(String key);
}

class Bar implements Foo {}
class Biz implements Foo {}

进行如下所示的错误已经完全有效:

FooImplMap m = ...;
m.put("b", new Bar());
Biz b = m.get("b"); // casting Bar to Biz

因此,我们也 可以 做到这一事实Integer i = m.get("b");并不是错误的
可能性。如果我们正在对这样的代码进行编程,那么一开始它就已经很不完善了。

通常,仅在没有理由绑定目标参数的情况下,才应仅从目标类型中推断出类型参数,例如Collections.emptyList()Optional.empty()

private static final Optional<?> EMPTY = new Optional<>();

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

可以,因为Optional.empty()既不能产生也不能消耗T



 类似资料:
  • Java中是否有一种方法可以通过一个方法的声明返回不同的类型? 我希望此方法返回一个对象,并在函数调用时将其转换为正确的类型。这就是我的想法,但它不是这样工作的。我是否需要某种通用返回类型来执行此操作?解决这个问题的最佳方法是什么?

  • 下面对getHighest()和getLowest()的调用返回Comparable类型的对象,而不是T类型的对象,这正是我们想要的。为什么,我该如何改进这段代码,使这些调用返回T(这样T的字段和方法就可用了)? 下一行生成编译器错误: 错误:找不到符号符号:方法getName()位置:接口java.lang.Comparable 我想employee.getHighest()返回一个员工(而不仅

  • 我正在阅读《有效Java》第5章关于泛型的内容,特别是关于偏好泛型方法的内容。我注意到,有时返回类型之前的方法声明中的类型参数(尖括号之间)会被省略。类似的案例有很多,但例如第二版第135页: 另一方面,我看到了类似的泛型方法与声明 第一个是错别字吗?如果不是,我什么时候可以省略声明中的括号? 谢啦

  • 问题内容: 背景 我曾经写过这种方法: 应该这样称呼它: 这很好用(尽管我在研究当前容易出错的问题时在这里的答案中已经看到)。 目前的情况 无论如何,现在我正在编写以下代码(在扩展javax.servlet.jsp.tagext.TagSupport的类中): 目的是可以这样称呼: 我的评估方法中的代码显然不起作用。的第二个参数应该是Class对象。这导致我: 我的问题 如何获得通用(返回)类型的

  • 在研究泛型时,我注意到泛型方法和泛型类型(类或接口)在类型引入语法上的一个差异使我感到困惑。 泛型方法的语法为 文件上说 为了彼此保持一致,我希望方法语法为 ,或者类型语法(for class)为,但事实显然并非如此。 为什么一个要介绍在前,另一个要介绍在后? 我主要以的形式使用泛型,并认为可能看起来很奇怪,但这是一个主观的参数,此外对于方法也是这样。您可以调用,类似于 在寻找技术解释时,我想在指

  • 问题内容: 考虑以下代码: 如果我用以下命令编译它: 它返回: 请注意,即使在返回类型上未引用任何泛型类型,也仅将泛型方法视为不安全。 这是一个错误吗?还是有一个更深层次的原因我没有考虑? 问题答案: 允许使用原始类型以确保与引入泛型之前编写的代码兼容。原始类型的工作原理是,仅从所有方法参数和返回类型中忽略 所有 类型信息,甚至与该类的类型参数无关的类型信息。正如您所发现的,这可能导致奇怪的结果。