为什么不能使用“new”运算符创建generics类型的实例?
我发现了很多关于如何克服这个限制的post,但没有关于为什么存在这种限制的post(除了这个 ,只是提到它与类型擦除有关)。
那么为什么不能创建generics类型的实例呢?
澄清一下,我的问题不是如何做到的。 我知道在C#中它是可能的,为什么不用Java呢? 我很好奇为什么Java人员没有实现类似的机制? 为什么迫使Java开发人员使用可能导致运行时错误的尴尬变通方法? 这种机制是否存在潜在危害?
简短回答: Java是一种编译的编程语言 ,这意味着您的字节码在运行时是常量。 如果E
未知,则无法为new E()
生成字节码。
说明 : 在运行时擦除通用信息:
public class Container { private E item; public E getItem() {return item;} } class BoxWithPresent extends Container { } class SimpleBox extends Container { }
在字节码类中, BoxWithPresent
包含Present
类型的字段item
,但类SimpleBox
包含Object
类型的字段item
(因为未指定类型E
)。
现在你编写抽象实例化方法:
public class Container { public E createE() { return new E(); // imagine if that was allowed } }
这里应该生成什么字节码? .class
文件现在是在编译时生成的,但我们不知道什么是E
类型。
那么..可以用new Object()
替换new T()
new Object()
吗? 不好的想法, BoxWithPresent
类不会喜欢它,因为它期望E
Present
。
它可以用class.newInstance()
替换吗? 同样不,方法范围中没有class
变量。
这就是为什么new E()
是不可能的。
但是有一些变通方法可以将class
作为参数传递,或者提取通用信息 。
最简单的答案是在运行时不存在generics类型参数。
在第5版中,generics被改进为Java语言。为了保持与现有代码库的向后兼容性,它们是通过擦除实现的。
源代码在编译时存在generics类型参数,但在编译期间,几乎所有证据都会在字节代码中被删除。 之所以选择这种generics,是因为它保持了前仿制代码和Java 5+通用代码之间的互操作性。 因此,generics的类型安全很大程度上只是编译时的现象。 如果您的通用代码编译没有错误且没有警告,那么您可以放心,您的代码是类型安全的。
但是,由于擦除,(从Java 5开始)有两种类型:
-
可兑现的 。 例如
String
,Integer
等。可编译类型在编译时具有与在运行时相同的类型信息。 -
不可兑现的 。 例如
List
,List
和T
不可重新生成的类型在编译时的运行时具有较少的类型信息。 实际上,上面的运行时类型是List
,List
和Object
。 在编译期间,擦除generics类型信息。
您不能将new
运算符与不可重复类型一起使用,因为在运行时没有类型安全的方法可以让JVM生成正确类型的对象。
源代码:
T myObject = new T();
以上不编译。 在运行时, T
已被删除。
规避类型擦除和Javagenerics的一些问题的策略是使用类型令牌 。 此策略在以下创建新T
对象的通用方法中实现:
public T newInstance(Class cls) { T myObject = cls.newInstance(); return myObject; }
generics方法从作为参数传递的Class
对象中捕获类型信息。 此参数称为类型标记。 不幸的是,类型标记本身必须始终是可恢复的(因为您不能获得不可重新类型的Class
对象),这可能会限制它们的用途。
请记住,generics类型与编译时的安全性有关。 类型的编译时检查允许编译器向您提供有关代码问题的警告/错误。 这并不能直接帮助您解决问题,但保持编译时和运行时的概念非常清晰非常重要。
你不能说,“ return new T()
”因为编译器无法知道如何做到这一点。 如果没有特定类型,编译器就无法知道要调用的构造函数,即使它存在。 此外,为了“新建”类型T
的实例,您需要调用一些东西。 T
只是一个象征。 它不是Class
或Object
。 在您提供有关T
的实例类型(例如List
)的信息之前,编译器无法执行您要执行的操作。
键入只是确保给定和返回的类型在编译时正确匹配的一种方法。 当您指定类型细节时,您可以创建它的实例。 这就是为什么你通常会在界面上看到完全开放的类型(例如List
, Function
等)。 因此,接口的实现者可以指定将使用的类型。
也许有必要考虑像模板这样的generics。 不是软件开发模式,而是更抽象的概念。 模板的概念说这个结构没有任何内部细节。 如果你想在模板后面创建这个东西,可以使用模板作为起点,但是你必须填写细节来制作东西。 generics类型有点像 – 它允许您构造某些东西但不提供有关该结构中发生的事情的任何细节。
我知道,当引入generics时,我对此非常挣扎。 起初我发现最简单的方法是首先编写特定类型的实现,然后使用generics将其抽象出来。 在我了解了generics之后,我现在发现更容易从一个通用接口开始然后实现它。
从generics创建类。 请注意,这取决于参数化的类。 这将返回generics的类对象,通过该类对象可以执行进一步的reflection以创建对象。
public static Class getClassFromGeneric( Object parentObj, int oridnalParamterizedTypeIndex) throws Exception{ Type[] typeArray = getParameterizedTypeListAsArray(parentObj); return (Class )typeArray[oridnalParamterizedTypeIndex]; } public static 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: http://stackoverflow.com/questions/23074446/java-lang-classcastexception-java-lang-class-cannot-be-cast-to-java-lang-reflec" + " for more info",e); throw e; } }
用法:
public class GenericBaseClass{} public class GenericImpl extends GenericBaseClass{ public static void main(String[] args){ new GenericImpl(); } public GenericImpl(){ Class tClazz = getClassFromGeneric(this,0); Constructor constructor = tClazz.getConstructor(); T newT = constructor.newInstance(); } }
与流行的看法相反,class级的通用信息不会被“删除”。