我找到了许多关于如何克服这个限制的帖子,但没有一个解释为什么会有这个限制(除了这篇,它只是提到这与类型擦除有关)。
那么为什么不能创建泛型类型的实例呢?
澄清一下,我的问题不是如何实现它。我知道在C#中是可能的,那么为什么Java不能呢?我想知道为什么Java开发人员没有实现类似的机制?为什么强制Java开发人员使用笨拙的解决方法,有可能导致运行时错误?这样的机制是否存在任何潜在危险?
我找到了许多关于如何克服这个限制的帖子,但没有一个解释为什么会有这个限制(除了这篇,它只是提到这与类型擦除有关)。
那么为什么不能创建泛型类型的实例呢?
澄清一下,我的问题不是如何实现它。我知道在C#中是可能的,那么为什么Java不能呢?我想知道为什么Java开发人员没有实现类似的机制?为什么强制Java开发人员使用笨拙的解决方法,有可能导致运行时错误?这样的机制是否存在任何潜在危险?
简短回答:
Java是一种编译型编程语言,这意味着在运行时您的字节码是固定的。如果E
未知,则不可能为new E()
生成字节码。
解释: 通用信息在运行时被擦除:
public class Container<E> {
private E item;
public E getItem() {return item;}
}
class BoxWithPresent extends Container<Present> {
}
class SimpleBox extends Container {
}
BoxWithPresent
中包含类型为Present
的字段item
,但是类SimpleBox
中包含类型为Object
的字段item
(因为未指定类型E
)。public class Container<E> {
public <E> E createE() {
return new E(); // imagine if that was allowed
}
}
这里应该生成什么字节码?目前在编译时会生成一个.class
文件,但我们不知道 E
类型。
那么...可以用 new Object()
替换 new T()
吗?这是个坏主意,因为 BoxWithPresent
类期望 E
是 Present
。
它可以被替换成 class.newInstance()
吗?同样不行,在方法范围内没有 class
变量。
这就是为什么 new E()
是不可能的。
但是有一些解决方法,比如将class
作为参数传递,或者提取泛型信息。
T myObject = new T();
public <T> T newInstance(Class<T> cls) {
T myObject = cls.newInstance();
return myObject;
}
Class
对象中捕获类型信息,这个参数叫做类型标记。不幸的是,类型标记本身必须始终是可具体化的(因为你不能为不可具体化的类型获取Class
对象),这可能会限制它们的实用性。T t = new T();
将变为Object o = new Object();
。因此,运行时不关心T
。它只会看到它需要创建一个Object类的实例并将其分配给Object类的引用。另外,您能否解释一下语句“因为并非所有对象都是T的对象”?赋值Object为什么会变得不安全?难道不会在编译时处理不安全性吗? - Yug Singhpublic class Test <T> { T var; public Test(T var) { this.var=var; } System.out.print(var); }
根据我的理解,在字节码中,T将被替换为Object,然后被转换为我们传递的任何参数(Integer,String等)。 为什么在这里 T t = new T(); 不能工作呢? - hamalT t = new T();
例程中编写的代码都是类型安全的。例如,可能会创建一个本应该是String
实例并将其分配给Integer
引用。此外,可能会将这些不安全创建的对象传递到远离实例化的代码中,并建立堆污染或在难以调试的位置引发异常。 - scottb public static <T> Class<T> getClassFromGeneric(
Object parentObj,
int oridnalParamterizedTypeIndex) throws Exception{
Type[] typeArray = getParameterizedTypeListAsArray(parentObj);
return (Class<T>)typeArray[oridnalParamterizedTypeIndex];
}
public static <T> Type[] getParameterizedTypeListAsArray(Object parentObj){
try{
return ((ParameterizedType) parentObj.getClass()
.getGenericSuperclass())
.getActualTypeArguments();
}
catch(ClassCastException e){
logger.log(Level.SEVERE, "Most likely, somewhere in your inhetirance chain,"
+ "there is a class that uses a raw type and not the generic param."
+ "See: https://dev59.com/UH7aa4cB1Zd3GeqPt7lm"
+ " for more info",e);
throw e;
}
}
使用方法:
public class GenericBaseClass<T>{}
public class GenericImpl extends GenericBaseClass<String>{
public static void main(String[] args){
new GenericImpl();
}
public GenericImpl(){
Class tClazz = getClassFromGeneric(this,0);
Constructor constructor = tClazz.getConstructor();
T newT = constructor.newInstance();
}
}
newT
会是什么类型。该类保留了String
的通用参数,因此newT
将是一个字符串。你可以自己运行它。相信我,当我第一次看到它时,我也觉得这是魔法。至于官方参考,请参见:https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/ParameterizedType.html#getActualTypeArguments%28%29 - user489041记得泛型类型是关于编译时的安全性。类型的编译时检查允许编译器向您发出有关代码问题的警告/错误。虽然这并没有直接帮助您的问题,但保持编译时和运行时的概念非常清晰很重要。
您不能说“return new T()
”,因为编译器无法知道如何执行此操作。没有具体的类型,编译器将不知道调用哪个构造函数,甚至不知道是否存在。另外,为了“new up”类型T
的实例,您需要调用某些内容。 T
只是一个符号。它不是一个Class
或Object
。在您提供有关T
实例的类型信息之前(例如List<String>
),编译器无法完成您尝试执行的操作。
打字只是一种确保编译时给定和返回的类型匹配的方式。当您指定类型细节时,才能创建其实例。这就是为什么您通常在接口上看到完全开放的类型(例如List<T>
,Function<T,R>
等)。因此,接口的实现者可以指定将使用哪些类型。
也许将泛型视为模板有所帮助。不是软件开发模式,而是更抽象的概念。模板的概念表示有这个结构,但没有任何内部细节。如果您想创建遵循模板的东西,请使用模板作为起点,但您必须填写详细信息以制作该物品。泛型类型有点像这样-它允许您构造某些内容,但不提供有关该结构中发生的任何详细信息。
我知道当引入泛型时,我曾经很困惑。起初,我发现先针对特定类型编写实现,然后再使用泛型进行抽象化最容易。在我理解泛型之后,我现在发现从泛型接口开始实现更容易。
T
是完全不同的。我以为这就是问题所在。 - MadConan
E e = new E()
。 - traveh