泛型类中的 Java 泛型方法

如果在 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);
  }
}

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

MyClass clazz = new MyClass();
String string = clazz.doSomething("String");
Integer integer = clazz.doSomething(1);

但是,如果我尝试使用 实例而不指定泛型类型,则无论传入的内容如何,我调用都会返回一个 :MyGenericClassdoSomething(K)ObjectK

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

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

MyGenericClass untyped = new MyGenericClass();
List<String> list = untyped.makeSingletonList("String"); // this compiles

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

MyGenericClass<?> wildcard = new MyGenericClass();
String string = wildcard.doSomething("String"); // this compiles
  • 在非类型化泛型类中调用泛型方法不起作用的原因是否有充分的理由?

  • 有没有一些与泛型类和泛型方法相关的聪明技巧,我错过了?

编辑:

为了澄清,我希望非类型化或原始类型的泛型类不遵循泛型类的类型参数(因为它们尚未提供)。但是,我不清楚为什么非类型化或原始类型的泛型类意味着泛型方法不被遵守。

事实证明,这个问题已经在SO,c.f.这个问题上提出来了。对此的回答解释了,当一个类是非类型化/以其原始形式时,所有泛型都从类中删除 - 包括泛型方法的类型化。

但是,对于为什么会这样,并没有真正的解释。因此,请允许我澄清我的问题:

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

编辑 - 讨论JLS:

有人建议(在回答前面的SO问题和这个问题时)在JLS 4.8中处理这个问题,其中指出:

未从其超类或超接口继承的原始类型 C 的构造函数 (§8.8)、实例方法 (§8.4、 §9.4) 或非静态字段 (§8.3) 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

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


答案 1

“为了向后兼容”似乎是类泛型类型擦除的充分理由 - 例如,需要它来允许您返回非类型化列表并将其传递给一些遗留代码。将其扩展到泛型方法似乎是一个棘手的子案例。

4.8 中的 JLS 代码段(您引用)涵盖了构造函数、实例方法和成员字段 - 泛型方法只是实例方法的一般情况。因此,此代码段似乎涵盖了您的情况。

使 JLS 4.8 适应此特定情况:

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

(此处,该方法的“类型”将包括所有参数和返回类型)。如果您将“擦除”解释为“擦除所有泛型”,那么这似乎确实与观察到的行为相匹配,尽管它不是很直观,甚至没有用。删除所有泛型,而不仅仅是泛型类参数,这似乎是一种过分热心的一致性(尽管我是谁来猜测设计者)。

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

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

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

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

(错误 + 修复表明,出于此类型擦除的目的,方法的返回类型被视为方法类型的一部分)

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

请求是修改类型擦除,以便在类型声明中,擦除仅从参数化类型中删除。然后,碰巧在 的声明中,擦除到 。Foo<T>TMap<K,V>Set<Map.Entry<K,V>>Set<Map.Entry>

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

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


答案 2

使用 Java 泛型,如果使用泛型类的原始形式,则该类上的所有泛型,甚至不相关的泛型方法(如 your 和 methods),都将变为原始泛型。据我所知,这样做的原因是提供与预泛型编写的Java代码的向后兼容性。makeSingletonListdoSomething

如果泛型类型参数 没有用处,则只需将其从 中删除,使方法泛型与 .否则,您将不得不接受这样一个事实,即必须将类类型参数提供给您的类,以便在类中的其他任何内容上使用泛型。TMyGenericClassKT


推荐