在我看来,这个问题非常有趣,它解决了几个不同的问题,值得得到不止一个答案,尽管那个答案可能完全正确。
为什么我需要在SomeClass前面[否则]会有一个...错误...不能对非静态类型T进行静态引用
在泛型类has certain restrictions 中静态成员、方法和字段的用法,请参见“不能声明类型为类型参数的静态字段”一节。尽管该部分讨论的是字段,而不是方法,但也可以将推理扩展到方法。事实上,如果我们同意声明
private static T foo = null;
没有意义,因为编译器无法创建与实例无关的变量foo,因为泛型类可以使用任何类型进行参数化(请记住,Java Generic 不是 C++ 模板,并且由于前者的类型擦除在运行时中只有一个(每个类加载器)泛型类的实例),那么为什么会这样
public static T get() {
return null;
}
应该更有意义吗?
其次,当您在 SomeClass 和 newInstance 方法中声明泛型类型 T 时,实际上您 隐藏第一个 T 和第二个 T,两者都是彼此无关。对于非静态类型,类型隐藏更为明显。在示例中
class Foo<T> {
<T> T get(){ // a warning "The type parameter T is hiding the type T"
return null;
}
}
第二个T,在方法中声明,隐藏第一个,为类声明,这就是你收到警告The type parameter T is hiding the type T的地方。要消除警告,您必须将方法中的 T 替换为另一个类型变量,例如Z,这样两者的区别就很明显了。
class Foo<T> {
<Z> Z get(){ // no warning, T and Z are different type variables
return null;
}
}
在静态方法的情况下,编译器不会发出警告(可能是因为它认为对于静态事物假设隐藏?),但在这种情况下也存在类型隐藏。在您提供的示例中,您成功地欺骗了 Java Generic 机器(恭喜:)),但是如果我们想象以下场景(我将您示例的语义稍微重构为更传统的语义)
static class Factory <T extends Number> {
private T t;
public static <T> T getInstance(){
return null;
}
public T get(){
return t;
}
}
,这两行都会编译
Number n1 = longFactory.get();
Number n2 = Factory.getInstance();
,但是你在第二个失去了类型安全,所以该行
String s1 = longFactory.get(); // fails to compile with "Type mismatch: cannot convert from Long to String" error message
失败了,这很好,但是这条线
String s2 = longFactory.getInstance(); // compiles only with a warning "static method should be accessed in a static way"
这是不好的,如果你错过了类型参数隐藏的点,这不是你所期望的。
(在后台,T longFactory.get() type-erases 到 Number longFactory.get(),而 static <T> T getInstance() type-erases 到 static Object getInstance(),例如
System.out.println(Factory.class.getMethod("getInstance").getReturnType().getSimpleName()); // prints "Object"
System.out.println(Factory.class.getMethod("get").getReturnType().getSimpleName()); // prints "Number"
但Type Erasure 是另一回事)