泛型类中的Java泛型方法

ama*_*ent 29 java generics language-design raw-types generic-method

如果在Java中创建泛型类(该类具有泛型类型参数),您可以使用泛型方法(该方法采用泛型类型参数)吗?

请考虑以下示例:

public class MyClass {
  public <K> K doSomething(K k){
    return k;
  }
}

public class MyGenericClass<T> {
  public <K> K doSomething(K k){
    return k;
  }

  public <K> List<K> makeSingletonList(K k){
    return Collections.singletonList(k);
  }
}
Run Code Online (Sandbox Code Playgroud)

正如您所期望的泛型方法,我可以调用任何对象的doSomething(K)实例MyClass:

MyClass clazz = new MyClass();
String string = clazz.doSomething("String");
Integer integer = clazz.doSomething(1);
Run Code Online (Sandbox Code Playgroud)

但是,如果我尝试使用MyGenericClass 没有指定泛型类型的实例,我调用doSomething(K)返回一个Object,无论K传入什么:

MyGenericClass untyped = new MyGenericClass();
// this doesn't compile - "Incompatible types. Required: String, Found: Object"
String string = untyped.doSomething("String");
Run Code Online (Sandbox Code Playgroud)

奇怪的是,如果返回类型是泛型类,它将编译 - 例如List<K>(实际上,这可以解释 - 请参阅下面的答案):

MyGenericClass untyped = new MyGenericClass();
List<String> list = untyped.makeSingletonList("String"); // this compiles
Run Code Online (Sandbox Code Playgroud)

此外,如果键入泛型类,它将编译,即使只使用通配符:

MyGenericClass<?> wildcard = new MyGenericClass();
String string = wildcard.doSomething("String"); // this compiles
Run Code Online (Sandbox Code Playgroud)
  • 有没有一个很好的理由为什么在无类型泛型类中调用泛型方法不起作用?

  • 是否有一些与我缺少的泛型类和泛型方法相关的巧妙技巧?

编辑:

为了澄清,我希望无类型或原始类型的泛型类不要遵守泛型类的类型参数(因为它们尚未提供).但是,我不清楚为什么无类型或原始类型的泛型类意味着泛型方法不受尊重.

据了解,这个问题已经在SO上提出过了.这个答案解释了当一个类是无类型/原始形式时,所有泛型都从类中删除 - 包括输入泛型方法.

但是,对于为什么会出现这种情况,并没有真正的解释.所以请允许我澄清我的问题:

  • 为什么Java会删除非类型化或原始类型泛型类的泛型方法?这有充分的理由,还是仅仅是疏忽?

编辑 - JLS的讨论:

有人建议(在回答之前的SO问题和这个问题时)在JLS 4.8中对此进行了处理,其中规定:

的类型构造函数(§8.8),实例方法的(8.4节,第9.4节),或者非静态字段(§8.3),其不从其超类或超接口继承的原类型C的M是原始类型对应在对应于C的通用声明中擦除其类型

我很清楚这与无类型类有什么关系 - 类的泛型类型被替换为擦除类型.如果类泛型被绑定,则擦除类型对应于那些边界.如果它们没有绑定,那么擦除类型是Object - 例如

// unbound class types
public class MyGenericClass<T> {
  public T doSomething(T t) { return t; }
}
MyGenericClass untyped = new MyGenericClass();
Object t = untyped.doSomething("String");

// bound class types
public class MyBoundedGenericClass<T extends Number> {
  public T doSomething(T t) { return t; }
}
MyBoundedGenericClass bounded = new MyBoundedGenericClass();
Object t1 = bounded.doSomething("String"); // does not compile
Number t2 = bounded.doSomething(1); // does compile
Run Code Online (Sandbox Code Playgroud)

虽然泛型方法是实例方法,但我不清楚JLS 4.8是否适用于泛型方法.泛型方法的类型(<K>在前面的示例中)不是无类型的,因为它的类型由方法参数确定 - 只有类是无类型/原始类型.

Gra*_*ths 8

"为了向后兼容"似乎是类通用类型类型擦除的充分理由 - 例如,它允许您返回无类型列表并将其传递给某些遗留代码.将其扩展为泛型方法似乎是一个棘手的子案例.

4.8中的JLS代码段(您引用它)涵盖了构造函数,实例方法和成员字段 - 泛型方法通常只是实例方法的一个特例.所以看来你的情况就是这个片段所涵盖的.

将JLS 4.8适应此特定情况:

泛型方法的类型是原始类型,对应于与C对应的泛型声明中其类型的擦除.

(这里方法的'type'将包括所有参数和返回类型).如果您将"擦除"解释为"擦除所有泛型",那么这似乎与观察到的行为相匹配,尽管它不是非常直观甚至有用.删除所有泛型,而不仅仅是泛型类参数(尽管我是第二次猜测设计者),这似乎是一种过于热心的一致性.

也许可能存在类通用参数与方法通用参数交互的问题 - 在您的代码中它们完全独立,但您可以想象其他情况下它们被分配/混合在一起.我认为值得指出的是,根据JLS,不推荐使用原始类型:

原始类型的使用仅允许作为遗留代码兼容性的让步.强烈建议不要在将通用性引入Java编程语言之后编写的代码中使用原始类型.未来版本的Java编程语言可能会禁止使用原始类型

Java开发人员的一些想法在这里很明显:

http://bugs.sun.com/view_bug.do?bug_id=6400189

(bug + fix显示方法的返回类型被视为此类型擦除目的的方法类型的一部分)

还有这个请求,有人似乎要求你描述的行为 - 只删除类通用参数,而不是其他泛型 - 但它被这个推理拒绝了:

请求是修改类型擦除,以便在类型声明中Foo<T>,擦除仅从T参数化类型中删除.然后,它发生在Map<K,V>'s声明中,Set<Map.Entry<K,V>>擦除到Set<Map.Entry>.

但如果Map<K,V>有一种采用类型的方法Map<String,V>,它的擦除就是这样Map<String>.对于类型擦除来改变类型参数的数量是可怕的,特别是对于编译时方法的解析.我们绝对不会接受这个要求.

期望能够使用原始类型(Map)同时仍然获得一些类型安全的泛型(Set<Map.Entry>)是太多了.