Java泛型是在Java 1.5中引入的。新语言特性的想法是永远不要破坏之前的版本。我们必须记住,泛型是语言/开发人员的类型安全功能。
为此,引入了两种新类型参数化类型
和类型变量
。
JLS 4.3 引用类型和值提出了以下TypeArgument
和TypeVariable
的语法。
ReferenceType:
ClassOrInterfaceType
TypeVariable
ArrayType
ClassOrInterfaceType:
ClassType
InterfaceType
ClassType:
TypeDeclSpecifier TypeArgumentsopt
InterfaceType:
TypeDeclSpecifier TypeArgumentsopt
TypeDeclSpecifier:
TypeName
ClassOrInterfaceType . Identifier
TypeName:
Identifier
TypeName . Identifier
TypeVariable:
Identifier
ArrayType:
Type [ ]
例如:
Vector<String>
Seq<Seq<A>>
Seq<String>.Zipper<Integer>
Collection<Integer>
Pair<String,String>
对于参数化类型
Vector<String> x = new Vector<String>();
Vector<Integer> y = new Vector<Integer>();
return x.getClass() == y.getClass();
每当没有给定边界时,它就会将其视为
java.lang.Object
,并通过类型擦除使其成为例如
Vector<Object>
,因此它与之前的Java版本向后兼容。
当类本身不是泛型时,泛型方法的语法如下。
来自JLs 8.4 Method Declarations
MethodDeclaration:
MethodHeader MethodBody
MethodHeader:
MethodModifiersopt TypeParametersopt Result MethodDeclarator Throwsopt
MethodDeclarator:
Identifier ( FormalParameterListopt )
一个示例看起来像这样
public class GenericMethod {
public static <T> T aMethod(T anObject) {
return anObject;
}
public static void main(String[] args) {
String greeting = "Hi";
String reply = aMethod(greeting);
}
}
这将导致类型擦除的结果为
public class GenericMethod {
public static Object aMethod(Object anObject) {
return anObject;
}
public static void main(String[] args) {
String greeting = "Hi";
String reply = (String) aMethod(greeting);
}
}
并且它还与之前的Java版本具有向下兼容性。详情请参阅两份提案文件以获得更深入的理解。
将泛型添加到Java编程语言:参与者草案规范
Java泛型类型的特化
关于技术部分。创建Java程序的步骤是编译
.java
文件。可以使用
javac
命令生成类文件。
JavacParser
解析上述规范的整个文件并生成字节码。请参见
此处获取JavacParser源代码。
让我们看一下以下Test.java文件。
class Things{}
class Stuff<T>{
T t;
public <U extends Things> U doStuff(T t, U u){
return u;
};
public <T> T doStuff(T t){
return t;
};
}
为了保持向后兼容,JVM没有改变类文件的先前属性。他们添加了一个新属性并将其命名为
Signature
。从提案论文中可以看到:
当用作方法或字段的属性时,签名给出该方法或字段的完整(可能是通用)类型。
当用作类属性时,签名指示类的类型参数,其超类型以及所有接口。
签名中的类型语法扩展到参数化类型和类型变量。还有一种新的形式类型参数的签名语法。
签名字符串的语法扩展如下:
JVM Spec 4.3.4定义了以下语法:
MethodTypeSignature:
FormalTypeParametersopt (TypeSignature*) ReturnType ThrowsSignature*
ReturnType:
TypeSignature
VoidDescriptor
ThrowsSignature:
^ ClassTypeSignature
^ TypeVariableSignature
通过使用
javap -v
命令对
Test.class
文件进行反汇编,我们得到以下内容:
class Stuff<T extends java.lang.Object> extends java.lang.Object
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #3.#20
#2 = Class #21
#3 = Class #22
#4 = Utf8 t
#5 = Utf8 Ljava/lang/Object;
#6 = Utf8 Signature
#7 = Utf8 TT;
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 doStuff
#13 = Utf8 (Ljava/lang/Object;LThings;)LThings;
#14 = Utf8 <U:LThings;>(TT;TU;)TU;
#15 = Utf8 (Ljava/lang/Object;)Ljava/lang/Object;
#16 = Utf8 <T:Ljava/lang/Object;>(TT;)TT;
#17 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object;
#18 = Utf8 SourceFile
#19 = Utf8 Test.java
#20 = NameAndType #8:#9
#21 = Utf8 Stuff
#22 = Utf8 java/lang/Object
Signature: #17
SourceFile: "Test.java"
这个方法
public <U extends Things> U doStuff(T t, U u){
return u;
};
将其翻译成Signature,以表示它是一种通用方法。
Signature: #14 // <U:LThings;>(TT;TU;)TU;
如果我们在之前的Java 1.5版本中使用非泛型类,例如:
public String doObjectStuff(Object t, String u){
return u;
}
会被翻译成
public java.lang.String doObjectStuff(java.lang.Object, java.lang.String)
descriptor: (Ljava/lang/Object
flags: ACC_PUBLIC
Code:
stack=1, locals=3, args_size=3
0: aload_2
1: areturn
LineNumberTable:
line 12: 0
两者之间唯一的区别在于一个具有“Signature”属性字段,表明它确实是一个通用方法,而另一个早期的Java 1.5版本则没有。但两者都具有相同的“descriptor”属性。
Non-Generic method
descriptor: (Ljava/lang/Object
Generic method
descriptor: (Ljava/lang/Object
这使它具有向后兼容性。因此答案就像你建议的那样,“语言设计者只是让它成为那个样子”,再加上“为了使其具有向后兼容性而不添加太多代码。”
HTML格式化:
这使它具有向后兼容性。因此答案就像你建议的那样,“语言设计者只是让它成为那个样子”,再加上“为了使其具有向后兼容性而不添加太多代码。”
编辑:关于评论中提到应该很容易处理不同的语法,我在Philip Wadler和Maurice Naftalin的《Java Generics and Collections》一书中找到了一段话。
Java中的泛型类似于C++中的模板。关于Java泛型和C++模板之间的关系,只有两件重要的事情需要记住:语法和语义。语法是故意相似的,而语义则是故意不同的。
在语法上,选择了尖括号,因为它们对于C++用户来说很熟悉,并且方括号很难解析。但是,语法上有一个区别。在C++中,嵌套参数需要额外的空格,因此您会看到像这样的东西:List< List > [...]等。
请参见此处