Today I stumbled upon some working Java code I wouldn't even have expected to compile. Reduced to its bare minimum, it looks like this:
今天我偶然发现了一些我甚至无法编译的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;
}
}
At first sight, the type parameter <T>
of the foo
methods in A
and B
look unnecessary since T
is not used anywhere else. Anyway, I found out that this is playing a crucial role in allowing the conflicting return value types to coexist in the same implementation: if one or both of the <T>
s are left out, the code doesn't compile. Here the non-working version:
乍一看,A和B中的foo方法的类型参数
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;
}
}
I don't need to fix the code snippets above as those are just examples I made up to explain my point. I'm only curious to understand why the compiler is behaving differently with them. Can someone explain what rules exactly are making the difference here?
我不需要修复上面的代码片段,因为这些只是我用来解释我的观点的例子。我只是很想知道编译器为什么表现不同。有人可以解释一下这些规则究竟有什么不同吗?
1 个解决方案
#1
21
While the first example does compile, it will give an unchecked conversion warning:
虽然第一个示例编译,但它会给出一个未经检查的转换警告:
// 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;
}
What's happening here is that by declaring type parameters, A.foo()
and B.foo()
are generic methods. Then, the overriding C.foo()
omits that type parameter. This is similar to using a raw type, essentially "opting out" of generic type checking for that method signature. That causes the compiler to use the inherited methods' erasures instead: List<String> foo()
and List<Integer> foo()
both become List foo()
, which can therefore be implemented by C.foo()
.
这里发生的是通过声明类型参数,A.foo()和B.foo()是通用方法。然后,重写的C.foo()省略了该类型参数。这类似于使用原始类型,基本上“选择退出”该方法签名的泛型类型检查。这导致编译器使用继承的方法的擦除:List
You can see that by keeping the type parameter in the C.foo()
declaration, there will be the expected compiler error instead:
您可以看到,通过在C.foo()声明中保留type参数,将会出现预期的编译器错误:
// The return type is incompatible with A.foo()
public <T> List<?> foo()
{
return null;
}
Likewise, if either of the interface methods don't declare a type parameter, then omitting a type parameter from the override fails to "opt out" of generic type checking for that method, and the return type List<?>
remains incompatible.
同样,如果任一接口方法未声明类型参数,则从覆盖中省略类型参数将无法“退出”该方法的泛型类型检查,并且返回类型List 仍然不兼容。
This behavior is covered in JLS §8.4.2:
JLS§8.4.2中介绍了此行为:
The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library.
子签名的概念旨在表达两种方法之间的关系,这两种方法的签名不相同,但其中一种方法可以覆盖另一种方法。具体来说,它允许一种方法,其签名不使用泛型类型来覆盖该方法的任何泛化版本。这很重要,因此库设计者可以独立于定义库的子类或子接口的客户端*地生成方法。
Angelika Langer's generics FAQ expands on this behavior in her section Can a non-generic method override a generic one?:
Angelika Langer的泛型FAQ在她的部分中扩展了这种行为。非泛型方法是否可以覆盖泛型方法?:
Now, let us explore an example where non-generic subtype methods override generic supertype methods. Non-generic subtype methods are considered overriding versions of the generic supertype methods if the signatures' erasures are identical.
现在,让我们探讨一个非泛型子类型方法覆盖泛型超类型方法的示例。如果签名的擦除相同,则非泛型子类型方法被认为是通用超类型方法的重写版本。
Example (of non-generic subtype methods overriding generic supertype methods):
示例(覆盖通用超类型方法的非泛型子类型方法):
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() {
Here the subtype methods have signatures, namely
set(Object)
andget()
, that are identical to the erasures of the supertype methods. These type-erased signatures are considered override-equivalent.这里的子类型方法具有签名,即set(Object)和get(),它们与超类型方法的擦除相同。这些类型擦除的签名被认为是覆盖等效的。
There is one blemish in the case of the
get
method: we receive an unchecked warning because the return types are not really compatible. The return type of the subtype methodget
isObject
, the return type of the supertype method get is an unbounded type parameter. The subtype method's return type is neither identical to the supertype method's return type nor is it a subtype thereof; in both situations the compiler would happily accept the return types as compatible. Instead, the subtype method's return typeObject
is convertible to the supertype method's return type by means of an unchecked conversion. An unchecked warning indicates that a type check is necessary that neither the compiler nor the virtual machine can perform. In other words, the unchecked operation is not type-safe. In case of the convertible return types someone would have to make sure that the subtype method's return value is type-compatible to the supertype method's return type, but nobody except the programmer can ensure this.在get方法的情况下有一个缺陷:我们收到一个未经检查的警告,因为返回类型并不真正兼容。子类方法get的返回类型是Object,超类型方法get的返回类型是*类型参数。子类型方法的返回类型既不与超类型方法的返回类型相同,也不是它的子类型;在这两种情况下,编译器都乐于接受返回类型兼容。相反,子类型方法的返回类型Object可以通过未经检查的转换转换为超类型方法的返回类型。未经检查的警告表示无需编译器和虚拟机执行类型检查。换句话说,未经检查的操作不是类型安全的。在可转换返回类型的情况下,有人必须确保子类型方法的返回值与超类型方法的返回类型类型兼容,但除了程序员之外没有人可以确保这一点。
#1
21
While the first example does compile, it will give an unchecked conversion warning:
虽然第一个示例编译,但它会给出一个未经检查的转换警告:
// 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;
}
What's happening here is that by declaring type parameters, A.foo()
and B.foo()
are generic methods. Then, the overriding C.foo()
omits that type parameter. This is similar to using a raw type, essentially "opting out" of generic type checking for that method signature. That causes the compiler to use the inherited methods' erasures instead: List<String> foo()
and List<Integer> foo()
both become List foo()
, which can therefore be implemented by C.foo()
.
这里发生的是通过声明类型参数,A.foo()和B.foo()是通用方法。然后,重写的C.foo()省略了该类型参数。这类似于使用原始类型,基本上“选择退出”该方法签名的泛型类型检查。这导致编译器使用继承的方法的擦除:List
You can see that by keeping the type parameter in the C.foo()
declaration, there will be the expected compiler error instead:
您可以看到,通过在C.foo()声明中保留type参数,将会出现预期的编译器错误:
// The return type is incompatible with A.foo()
public <T> List<?> foo()
{
return null;
}
Likewise, if either of the interface methods don't declare a type parameter, then omitting a type parameter from the override fails to "opt out" of generic type checking for that method, and the return type List<?>
remains incompatible.
同样,如果任一接口方法未声明类型参数,则从覆盖中省略类型参数将无法“退出”该方法的泛型类型检查,并且返回类型List 仍然不兼容。
This behavior is covered in JLS §8.4.2:
JLS§8.4.2中介绍了此行为:
The notion of subsignature is designed to express a relationship between two methods whose signatures are not identical, but in which one may override the other. Specifically, it allows a method whose signature does not use generic types to override any generified version of that method. This is important so that library designers may freely generify methods independently of clients that define subclasses or subinterfaces of the library.
子签名的概念旨在表达两种方法之间的关系,这两种方法的签名不相同,但其中一种方法可以覆盖另一种方法。具体来说,它允许一种方法,其签名不使用泛型类型来覆盖该方法的任何泛化版本。这很重要,因此库设计者可以独立于定义库的子类或子接口的客户端*地生成方法。
Angelika Langer's generics FAQ expands on this behavior in her section Can a non-generic method override a generic one?:
Angelika Langer的泛型FAQ在她的部分中扩展了这种行为。非泛型方法是否可以覆盖泛型方法?:
Now, let us explore an example where non-generic subtype methods override generic supertype methods. Non-generic subtype methods are considered overriding versions of the generic supertype methods if the signatures' erasures are identical.
现在,让我们探讨一个非泛型子类型方法覆盖泛型超类型方法的示例。如果签名的擦除相同,则非泛型子类型方法被认为是通用超类型方法的重写版本。
Example (of non-generic subtype methods overriding generic supertype methods):
示例(覆盖通用超类型方法的非泛型子类型方法):
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() {
Here the subtype methods have signatures, namely
set(Object)
andget()
, that are identical to the erasures of the supertype methods. These type-erased signatures are considered override-equivalent.这里的子类型方法具有签名,即set(Object)和get(),它们与超类型方法的擦除相同。这些类型擦除的签名被认为是覆盖等效的。
There is one blemish in the case of the
get
method: we receive an unchecked warning because the return types are not really compatible. The return type of the subtype methodget
isObject
, the return type of the supertype method get is an unbounded type parameter. The subtype method's return type is neither identical to the supertype method's return type nor is it a subtype thereof; in both situations the compiler would happily accept the return types as compatible. Instead, the subtype method's return typeObject
is convertible to the supertype method's return type by means of an unchecked conversion. An unchecked warning indicates that a type check is necessary that neither the compiler nor the virtual machine can perform. In other words, the unchecked operation is not type-safe. In case of the convertible return types someone would have to make sure that the subtype method's return value is type-compatible to the supertype method's return type, but nobody except the programmer can ensure this.在get方法的情况下有一个缺陷:我们收到一个未经检查的警告,因为返回类型并不真正兼容。子类方法get的返回类型是Object,超类型方法get的返回类型是*类型参数。子类型方法的返回类型既不与超类型方法的返回类型相同,也不是它的子类型;在这两种情况下,编译器都乐于接受返回类型兼容。相反,子类型方法的返回类型Object可以通过未经检查的转换转换为超类型方法的返回类型。未经检查的警告表示无需编译器和虚拟机执行类型检查。换句话说,未经检查的操作不是类型安全的。在可转换返回类型的情况下,有人必须确保子类型方法的返回值与超类型方法的返回类型类型兼容,但除了程序员之外没有人可以确保这一点。