泛型方法实现中的不同返回值类型 [英] Different return value types in implementation of generic methods

查看:482
本文介绍了泛型方法实现中的不同返回值类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

今天,我偶然发现了一些我不希望编译的工作Java代码。减少到最低限度,它看起来像这样:

  import java.util.List; 

interface A {
< T>列表与LT;字符串> FOO();
}

interface B {
< T>列表与LT;整数> FOO();
}

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


$ / code>

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

  import java.util.List; 

接口A {
列表< String> FOO();
}

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

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

我不需要修正上面的代码片段因为这些只是我用来解释我的观点的例子。我只是想知道为什么编译器的行为与他们不同。有人可以解释一下哪些规则正在改变这里吗?

警告:

  //类型安全性:返回类型List<?>对于C类型的foo()需要
//未经检查的转换以符合List< String>
public List<?> foo()
{
return null;
}

这里发生的事情是通过声明类型参数 A.foo() B.foo()通用方法。然后,覆盖 C.foo()省略该类型参数。这与使用原始类型,实质上是对该方法签名的泛型类型检查选择退出。这会导致编译器使用继承的方法 erasures 而不是:列表< String> foo() List< Integer> foo()都变成 List foo(),因此可以通过 C.foo()

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

  //返回类型与A.foo()$ b $不兼容b public< T>列表与LT;?> foo()
{
return null;
}

同样,如果任一接口方法没有声明类型参数,那么忽略覆盖中的类型参数将无法退出该方法的泛型类型检查,并且返回类型 List <?>< / code>仍然不兼容。



此行为在 JLS§8.4.2


subignature的概念是为了表示两个方法之间的关系,这两个方法的签名不相同,但其中一个可能会覆盖另一个。具体来说,它允许一种方法,其签名不使用泛型来覆盖该方法的任何泛型化版本。这非常重要,这样图书馆设计人员就可以独立于定义库的子类或子接口的客户端自由生成方法。


Angelika Langer的泛型常见问题解答在她的部分可以扩展此行为吗?非泛型方法可以覆盖泛型方法吗?
$ b


现在,让我们探讨一个非泛型子类型方法
覆盖泛型超类型方法的示例。如果
签名的删除是相同的,则非泛型子类型方法是
被认为是泛型超类型方法的替代。



泛型子类型方法覆盖通用超类型
方法):

pre $ class $ {b $ b public< T> void set(T arg){...}
public< T> T get(){...}
}
class Sub扩展超级{
public void set(Object arg){...} //覆盖
public Object get ){...} //用未经检查的警告覆盖
}






警告:get中的get()覆盖Super中的< T> get
返回类型需要未经检查的转换
found:Object
required:T
public Object get(){

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

get 方法中有一个缺陷:我们收到一个
unchecked警告,因为返回类型不是真正的兼容。
子类型方法 get 的返回类型是 Object ,返回类型为
超类型方法get是一个无界的类型参数。
子类型方法的返回类型既不与超类型
方法的返回类型相同,也不是它的子类型;在这两种情况下
编译器会高兴地接受返回类型为兼容的。
相反,子类型方法的返回类型 Object 可以通过未经检查的转换转换为
超类型方法的返回类型。
未经检查的警告表明需要类型检查,
既不是编译器也不是虚拟机可以执行的。在其他
字中,未经检查的操作不是类型安全的。在
可转换返回类型的情况下,有人必须确保
子类型方法的返回值与超类型
方法的返回类型是类型兼容的,但程序员除了可以确保
this。



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:

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:

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?

解决方案

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().

You can see that by keeping the type parameter in the C.foo() declaration, there will be the expected compiler error instead:

// 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.

This behavior is covered in 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?:

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) and get() , that are identical to the erasures of the supertype methods. These type-erased signatures are considered override-equivalent.

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 method get is Object , 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 type Object 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.

这篇关于泛型方法实现中的不同返回值类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆