generics类中的Javagenerics方法

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

请考虑以下示例:

public class MyClass { public  K doSomething(K k){ return k; } } public class MyGenericClass { public  K doSomething(K k){ return k; } public  List makeSingletonList(K k){ return Collections.singletonList(k); } } 

正如您对通用方法所期望的那样,我可以使用任何对象在MyClass实例上调用doSomething(K)

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

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

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

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

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

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

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

  • 是否有一些与我遗漏的generics类和generics方法相关的巧妙技巧?

编辑:

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

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

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

  • 为什么Java会删除无类型或原始类型generics类的generics方法? 有这么好的理由,还是仅仅是疏忽?

编辑 – JLS的讨论:

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

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

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

 // unbound class types public class MyGenericClass { public T doSomething(T t) { return t; } } MyGenericClass untyped = new MyGenericClass(); Object t = untyped.doSomething("String"); // bound class types public class MyBoundedGenericClass { 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 

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

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

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

将JLS 4.8适应此特定情况:

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

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

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

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

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

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

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

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

请求是修改类型擦除,以便在类型声明Foo ,擦除仅从参数化类型中删除T 然后,在Map的声明中, Set>擦除到Set

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

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

对于Java Generics,如果使用generics类的原始forms,那么类中的所有generics,甚至不相关的generics方法(例如makeSingletonListdoSomething方法)都将变为原始类。 我理解它的原因是提供向前兼容Java代码的向前兼容性。

如果您的generics类型参数T没有用处,那么只需将其从MyGenericClass删除,使您的方法与K 。 否则,你必须忍受这样一个事实,即类类型参数T必须提供给你的类,以便在类中的任何其他东西上使用generics。

我发现了完全丢弃generics的一个原因(但它并不是很好)。 原因是:仿制药可能有限。 考虑这个课程:

 public static class MyGenericClass { public  K doSomething(K k){ return k; } public  List makeSingletonList(K k){ return Collections.singletonList(k); } } 

当你使用没有generics的类时,编译器必须丢弃doSomethinggenerics。 而且我认为所有的generics都被丢弃以与这种行为保持一致。

makeSingletonList编译,因为Java从ListList进行未经检查的强制转换(但编译器显示警告)。

这并没有回答基本问题,但确实解决了为什么makeSingletonList(...)编译而doSomething(...)没有编译的问题:

MyGenericClass的无类型实现中:

 MyGenericClass untyped = new MyGenericClass(); List list = untyped.makeSingletonList("String"); 

……相当于:

 MyGenericClass untyped = new MyGenericClass(); List list = untyped.makeSingletonList("String"); List typedList = list; 

这将编译(带有几个警告),但随后会打开运行时错误。

实际上,这也类似于:

 MyGenericClass untyped = new MyGenericClass(); List list = untyped.makeSingletonList("String"); // this compiles!!! Integer a = list.get(0); 

…当您尝试获取String值并将其转换为Integer时,它会编译但会抛出运行时ClassCastException

这样做的原因是与预先generics代码向后兼容。 pre-generics代码不使用generics参数,而是使用今天似乎是原始类型的东西。 pre-generics代码将使用Object引用而不是使用generics类型的引用,而raw类型对所有generics参数使用Object类型,因此代码确实向后兼容。

例如,请考虑以下代码:

 List list = new ArrayList(); 

这是预通用代码,一旦引入generics,这被解释为原始generics类型等效于:

 List list = new ArrayList<>(); 

因为? 在它之后没有extendssuper关键字,它被转换为:

 List list = new ArrayList<>(); 

在generics使用Object引用列表元素之前使用的List版本,此版本还使用Object引用列表元素,因此保留向后兼容性。

从我对MAnyKeys的评论回答:

我认为完整类型的擦除与上面提到的向后兼容性相结合是有意义的。 当创建没有generics类型参数的对象时,它可以(或者应该是?)假定对象的使用是在非通用代码中进行的。

考虑这个遗留代码:

 public class MyGenericClass { public Object doSomething(Object k){ return k; } } 

像这样叫:

 MyGenericClass foo = new MyGenricClass(); NotKType notKType = foo.doSomething(new NotKType()); 

现在这个类和它的方法是通用的:

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

现在上面的调用者代码将不再编译,因为NotKType不是KType的超类型。 为避免这种情况,generics类型将替换为Object 。 虽然有些情况下它不会产生任何差别(如您的示例),但编译器分析时间至少非常复杂。 甚至可能是不可能的。

我知道这个szenario似乎有点构建,但我确信它会不时发生。