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

GOT*_*O 0 34 java generics interface

今天我偶然发现了一些我甚至无法编译的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

乍一看,类型参数<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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我不需要修复上面的代码片段,因为这些只是我用来解释我的观点的例子.我只是很想知道编译器为什么表现不同.有人可以解释一下这些规则究竟有什么不同吗?

Pau*_*ora 21

虽然第一个示例编译,但它会给出一个未经检查的转换警告:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

这里发生的是通过声明类型参数,A.foo()并且B.foo()通用方法.然后,覆盖C.foo()省略该类型参数.这与使用原始类型类似,基本上"选择退出"该方法签名的泛型类型检查.这导致编译器使用继承的方法的擦除:List<String> foo()并且List<Integer> foo()两者都变成了List foo(),因此可以通过它来实现C.foo().

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

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

同样,如果任一接口方法未声明类型参数,则从覆盖中省略类型参数将无法"退出"该方法的泛型类型检查,并且返回类型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 
} 
Run Code Online (Sandbox Code Playgroud)
warning: get() in Sub overrides <T>get() in Super;  
return type requires unchecked conversion 
found   : Object 
required: T 
        public Object get() { 
Run Code Online (Sandbox Code Playgroud)

这里亚型方法有签名,即set(Object)get() ,这是相同的超型方法擦除.这些类型擦除的签名被认为是覆盖等效的.

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