问题描述
如果您在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>列表与LT; K> makeSingletonList(K k){
return Collections.singletonList(k);
code
$ b正如你所期望的使用泛型方法,我可以使用任何对象对 MyClass 实例调用 doSomething(K):
MyClass clazz = new MyClass();
String string = clazz.doSomething(String);
Integer integer = clazz.doSomething(1);
但是,如果我尝试使用 MyGenericClass $ c $没有指定泛型类型,
我调用 doSomething(K)返回一个 Object ,无论 K MyGenericClass untyped = new MyGenericClass();
//这不能编译 - 不兼容的类型,必需:字符串,找到:对象
String string = untyped.doSomething(String);
奇怪的是,如果返回类型是泛型类, 列表< K> (实际上,这可以解释 - 见下面的答案):
MyGenericClass untyped = new MyGenericClass();
列表< String> list = untyped.makeSingletonList(String); //编译
另外,如果泛型类是键入的,即使只使用通配符:
MyGenericClass<?> wildcard = new MyGenericClass();
String string = wildcard.doSomething(String); //这个编译
-
在一个无类型的泛型类中的泛型方法不应该工作吗?
-
是否有一些与我缺少的泛型类和泛型方法有关的巧妙技巧?
- 为什么Java会删除在无类型或原始类型泛型类上的泛型方法?是否有这样的理由,还是只是一个疏忽?
$ b
$ b 编辑:
为了澄清,我预计未定义类型或原始类型的泛型类不遵守泛型类的类型参数(因为它们尚未提供)。然而,我不清楚为什么一个无类型或原始类型的泛型类将意味着泛型方法不被尊重。
这个问题已经在SO,c.f上提出了。 。这个答案解释了当一个类是非类型化的/以其原始形式时,所有的泛型从类中移除 - 包括泛型方法的输入。
$ b $然而,对于这种情况,并没有真正的解释。因此,请允许我澄清我的问题:
编辑 - 讨论JLS:
有人建议(回答前面的SO问题和这个问题),这是在,其中声明:
我很清楚这与此有关到一个无类型的类 - 类泛型类型被替换为删除类型。如果类泛型被绑定,那么擦除类型对应于这些边界。如果它们没有被绑定,那么擦除类型是对象 - 例如,
//未绑定的类类型
public class MyGenericClass< T> {
public T doSomething(T t){return t; }
}
MyGenericClass untyped = new MyGenericClass();
Object t = untyped.doSomething(String);
//绑定的类类型
public class MyBoundedGenericClass< T extends Number> {
public T doSomething(T t){return t; }
}
MyBoundedGenericClass bounded = new MyBoundedGenericClass();
Object t1 = bounded.doSomething(String); //不会编译
数字t2 = bounded.doSomething(1); //不会编译
虽然泛型方法是实例方法,但我不清楚JLS 4.8是否适用到通用方法。泛型方法的类型(前面例子中的< K> )不是无类型的,因为它的类型是由方法参数决定的 - 只有类是无类型/原始类型的。
'为了向后兼容'似乎是类泛型类型擦除的充分理由 - 它需要允许您返回一个无类型的列表并将其传递给一些遗留代码。
4.8中的JLS代码段(您引用的内容)涵盖了构造函数,实例方法和成员字段 - 泛型方法通常只是实例方法的一个特例。所以看起来你的情况是由这段代码覆盖的。
将JLS 4.8调整为这种特定情况:
(这里方法的'type'包含所有参数和返回类型)。如果你将'擦除'理解为'擦除所有的泛型',那么这看起来似乎与观察到的行为相符,尽管它不是非常直观或者甚至是有用的。它几乎看起来像一个过分热衷的一致性,以消除所有的泛型,而不仅仅是泛型类参数(尽管我是谁来猜测设计者)。
也许可能会出现类泛型参数与方法泛型参数交互的问题 - 在您的代码中它们完全独立,但您可以想象其他情况下它们被分配/混合在一起。我认为值得指出的是,不建议使用原始类型,根据JLS:
一些思考Java开发人员在这里显而易见:
(bug + fix显示方法的返回类型被视为这种擦除类型的方法的一部分)
还有,其中有人似乎要求你描述的行为 - 只擦除类的泛型参数,而不是其他泛型 - 但它被拒绝了这个推理:
If you create a generic class in Java (the class has generic type parameters), can you use generic methods (the method takes generic type parameters)?
Consider the following example:
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); } }
As you would expect with a generic method, I can call doSomething(K) on instances of MyClass with any object:
MyClass clazz = new MyClass(); String string = clazz.doSomething("String"); Integer integer = clazz.doSomething(1);
However, if I try to use instances of MyGenericClass without specifying a generic type,I calling doSomething(K) returns an Object, regardless of what K was passed in:
MyGenericClass untyped = new MyGenericClass(); // this doesn't compile - "Incompatible types. Required: String, Found: Object" String string = untyped.doSomething("String");
Oddly, it will compile if the return type is a generic class - e.g. List<K> (Actually, this can be explained - see answer below):
MyGenericClass untyped = new MyGenericClass(); List<String> list = untyped.makeSingletonList("String"); // this compiles
Also, it will compile if the generic class is typed, even if only with wildcards:
MyGenericClass<?> wildcard = new MyGenericClass(); String string = wildcard.doSomething("String"); // this compiles
Is there a good reason why calling a generic method in an untyped generic class shouldn't work?
Is there some clever trick relating to generic classes and generic methods that I am missing?
EDIT:
To clarify, I would expect an untyped or raw-typed generic class not to honour the generic class's type parameters (because they haven't been provided). However, it's not clear to my why an untyped or raw-typed generic class would mean that generic methods are not honoured.
It transpires that this issue has already been raised on SO, c.f. this question. The answers to this explain that when a class is untyped / in its raw-form, all generics are removed from the class - including typing of generic methods.
However, there isn't really an explanation as to why this is the case. So allow me to clarify my question:
- Why does Java remove generic method typing on untyped or raw-type generic classes? Is there a good reason for this, or was it just an oversight?
EDIT - discussion of JLS:
It has been suggested (in answer to the previous SO question and to this question) that this is treated in JLS 4.8, which states:
It is clear to me how this relates to an untyped class - the class generic types are replaced with the erasure types. If the class generics are bound, then the erasure type corresponds to those bounds. If the they are not bound, then the erasure type is Object - e.g.
// 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
Whilst generic methods are instance methods, it is not clear to me that JLS 4.8 applies to generic methods. The generic method's type (<K> in earlier example) is not untyped, as it's type is determined by the method parameters - only the class is untyped / raw-typed.
'for backwards compatibility' seems a sufficient reason for the type erasure of class generic types - it is needed e.g. to allow you to return an untyped List and pass it to some legacy code. The extension of this to generic methods seems like a tricky sub-case.
The JLS snippet from 4.8 (which you quote) covers constructors, instance methods and member fields - generic methods are just a particular case of instance methods in general. So it seems your case is covered by this snippet.
Adapting JLS 4.8 to this specific case :
(here the 'type' of the method would include all parameter and return types). If you interpret 'erasure' as 'erasing all generics', then this does seem to match the observed behaviour, although it is not very intuitive or even useful. It almost seems like an overzealous consistency, to erase all generics, rather than just generic class parameters (although who am I to second guess the designers).
Perhaps there could be problems where the class generic parameters interact with the method generic parameters - in your code they are fully independent, but you could imagine other cases where they are assigned / mixed together. I think it's worth pointing out that use of raw types are not recommended, as per the JLS :
Some of the thinking of the java developers is apparent here :
http://bugs.sun.com/view_bug.do?bug_id=6400189
(bug + fix showing that a method's return type is treated as part of the method's type for the purposes of this type erasure)
There is also this request, where someone appears to request the behaviour you describe - only erase the class generic parameters, not other generics - but it was rejected with this reasoning:
这篇关于泛型类中的Java泛型方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!