一尘不染

通用方法实现中的不同返回值类型

java

今天,我偶然发现了一些我什至没想到可以编译的Java代码。减少到最低限度,它看起来像这样:

import java.util.List;

interface A {
    <T> List<String> foo();
}

interface B {
    <T> List<Integer> foo();
}

class C implements A, B {
    @Override
    public List<?> foo()
    {
        return null;
    }
}

乍一看,类型参数<T>foo的方法AB期待,因为没有必要T,不使用其他任何地方。无论如何,我发现这在允许冲突的返回值类型共存于同一实现中起着至关重要的作用:如果<T>忽略了一个或两个,则代码不会编译。这里是非工作版本:

import java.util.List;

interface A {
    List<String> foo();
}

interface B {
    List<Integer> foo();
}

class C implements A, B {
    @Override
    public List<?> foo()
    {
        return null;
    }
}

我不需要修复上面的代码片段,因为这些只是我用来解释我观点的示例。我只是想知道为什么编译器对它们的行为有所不同。有人可以解释到底有什么规则在起作用吗?


阅读 297

收藏
2020-12-03

共1个答案

一尘不染

虽然第一个示例确实进行了编译,但将给出未经检查的转换警告:

// Type safety: The return type List<?> for foo() from the type C needs
// unchecked conversion to conform to List<String>
public List<?> foo()
{
    return null;
}

这里发生的事情是,通过声明类型的参数,A.foo()并且B.foo()通用的方法。然后,覆盖C.foo()忽略该类型参数。这类似于使用原始类型,本质上是“退出”该方法签名的通用类型检查。这会导致使用该继承的方法的编译器
擦除

代替:List<String> foo()List<Integer> foo()二者成为List foo(),其因此可以通过被实现C.foo()

您可以看到,通过将类型参数保留在C.foo()声明中,将会出现预期的编译器错误:

// The return type is incompatible with A.foo()
public <T> List<?> foo()
{
    return null;
}

同样,如果两个接口方法中的任何一个都不声明类型参数,则从覆盖中省略类型参数将无法“选择退出”该方法的通用类型检查,并且返回类型List<?>仍然不兼容。

JLS§8.4.2涵盖了此行为:

子签名的概念旨在表示两种方法之间的关系,这些方法的签名不完全相同,但是其中一个可以覆盖另一个方法。具体来说,它允许签名不使用泛型类型的方法覆盖该方法的任何泛化版本。这很重要,因此库设计者可以独立于定义库的子类或子接口的客户端自由地泛化方法。

Angelika
Langer的泛型常见问题解答在其部分中对此行为进行了扩展。非泛型方法可以覆盖泛型方法吗?

现在,让我们探讨一个非泛型子类型方法覆盖泛型超类型方法的示例。如果签名的擦除相同,则将非通用子类型方法视为通用超类型方法的替代版本。

示例(非泛型子类型方法覆盖泛型超类型方法):

class Super {
  public <T> void set( T arg) { ... }
  public <T> T get() { ... }
}
class Sub extends Super {
  public void set( Object arg) { ... } // overrides
  public Object get() { ... }    // overrides with unchecked warning
}

warning: get() in Sub overrides <T>get() in Super;  
return type requires unchecked conversion
found   : Object
required: T
        public Object get() {

在此,子类型方法具有签名,即set(Object)get() ,与超级类型方法的擦除相同。这些类型擦除的签名被视为等效。

get方法有一个缺陷:因为返回类型不是真正兼容的,所以我们收到未经检查的警告。亚型方法的返回类型getObject,该超GET方法的返回类型是一个无限制类型参数。子类型方法的返回类型既不与超类型方法的返回类型相同,也不是其子类型。在这两种情况下,编译器都会欣然接受兼容的返回类型。而是子类型方法的返回类型Object可以通过未经检查的转换转换为超类型方法的返回类型。未经检查的警告表明,必须执行类型检查,编译器和虚拟机均不能执行该检查。换句话说,未检查的操作不是类型安全的。如果是可转换的返回类型,则必须确保子类型方法的返回值与超类型方法的返回类型在类型上兼容,但是除程序员之外,没有人可以确保这一点。

2020-12-03