1.为什么引入泛型
结果:
以上的测试,可以看出Java SE 5.0之前,对ArrayList的使用存在两个问题:
(1)当获取一个值(使用get()方法)时,必须进行强制类型转换
(2)没有错误检查,可以添加任何对象。
泛型提供了一个更好的解决方案:[b]类型参数(type parameters)[/b]
[b]ArrayList类有一个类型参数用来指示元素的类型[/b]:
ArrayList<String> al = new ArrayList<String>();
当调用get方法时不需要强制类型转换;使用add方法时,编译器将检查元素的类型。
结果:
2.简单泛型类的定义
一个泛型类(generic class)就是具有一个或多个[b]类型变量(type variables)[/b]的类。
例如:Pair<T>泛型类型
Pair类引入了一个[b]类型变量T[/b],用尖括号(<>)括起来,并放在类名后面。
[b]泛型类可以有多个类型变量[/b]:
public class Pair<T, U>{...}
[b]类定义中的类型变量可指定方法的返回值以及域和局部变量的类型[/b]:
public T getFirst(){}
praivate T first;
public void setFirst(T newValue){}
注释:类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示集合的元素类型,K和V表示表的关键字与值的类型。T(需要时还可以用临时的字母U和S)表示“任何类型”。
用[b]具体的类型[/b]替换[b]类型变量[/b]就可以[b]实例化泛型类型[/b],例如:
Pair<String>
可以将结果(Pair<String>)想象成带有构造器的普通类:
Pair<String>()
Pair<String>(String, String)
和方法:
String getFirst()
String getSecond()
void setFirst(String)
void setSecond(String)
使用Pair类:
计算静态的minmax方法遍历数组,并计算出最小值和最大值,用一个Pair对象返回这两个结果。
结果:
3.泛型方法
前面已经介绍了如何定义一个泛型类,下面介绍定义一个带有类型参数的方法:
这是一个泛型方法。
泛型方法的格式:
泛型方法可以定义在普通方法中,也可以定义在泛型类中。
当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
在这种情况下(实际也是大多数情况)下,方法调用中可以省略<String>类型参数。编译器有足够的信息能够推断出所调用的方法。它用names的类型(即String[])与泛型类型T[]进行匹配并推断出T一定是String。也就是说,可以调用
大多数情况下,对于泛型方法的类型引用没有问题。偶尔,编译器也会提示错误,此时需要编译错误报告。
4.类型变量的限定(Bounds for Type Variables)
有时候,类或方法需要对类型变量加以约束。
计算数组中最小的元素:
这里存在一个问题。方法min中,变量smallest的类型为T,这意味着其可以是任何一个类型的对象,由此便不能确定T所属的类型是否有compareTo方法。因此此处会提示编译错误:
解决方案:将T限制为实现了Comparable接口(只含一个方法compareTo的标准接口)的类。可以通过对类型变量设置限定(bound)实现这一点:
实际上Comparable接口本身就是一个泛型类型。目前,我们忽略其复杂性以及编译器产生的警告。
现在,泛型的min方法只能被实现了Comparable接口的类(如String、Date等)的数组调用。
表示T应该是绑定类型(bounding type)的子类型(subtype)。T和绑定类型可以是类,也可以是接口。
一个类型变量或通配符可以有多个限定(multiple bounds),例如
限定类型(bounding types)用“&”分隔,而逗号用来分隔类型变量(type variables)。
在Java的继承中,可以有任意多个接口超类型,但最多只有一个限定是类。如果有一个类作为限定,它必须是限定列表中的第一个。
(As with Java inheritance, you can have as many interface supertypes as you like, but at most one of the bounds can be a class. If you have a class as a bound, it must be the first one in the bounds list. )
例子:重新编写一个泛型方法minmax,该方法计算出泛型数字中的最小值和最大值,并返回Pair<T>。
5.泛型代码与虚拟机
[b]虚拟机没有泛型类型对象——所有对象都属于普通类。[/b]
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是移除类型参数(type parameters)后的泛型类型名。类型变量(type variables)被擦除(erased),并且被替换为它的限定类型(bounding types)(或者是Object对于无限定的变量)。
例如,Pair<T>的原始类型如下;
因为T是一个无限定的变量(unbounded type variable),所以直接使用Object替换。
结果是一个普通的类,就好像泛型引入Java语言之前已经实现的那样。
在程序中可以包含不同类型的Pair,例如,Pair<String>或Pair<Integer>。而擦除类型后就变成原始的Pair类型了。
package generic;
import java.util.ArrayList;
public class ArrayListTest{
public static void main(String[] args){
ArrayList al = new ArrayList();
al.add(1);
System.out.println(al);
Integer i1 = (Integer)al.get(0);
System.out.println(i1);
al.add("hello");
/*
* 下面的代码编译正确,运行时报错
* Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
* */
Integer i2 = (Integer)al.get(1);
System.out.println(i2);
}
}
结果:
[1]
1
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at generic.ArrayListTest.main(ArrayListTest.java:18)
以上的测试,可以看出Java SE 5.0之前,对ArrayList的使用存在两个问题:
(1)当获取一个值(使用get()方法)时,必须进行强制类型转换
(2)没有错误检查,可以添加任何对象。
泛型提供了一个更好的解决方案:[b]类型参数(type parameters)[/b]
[b]ArrayList类有一个类型参数用来指示元素的类型[/b]:
ArrayList<String> al = new ArrayList<String>();
当调用get方法时不需要强制类型转换;使用add方法时,编译器将检查元素的类型。
package generic;
import java.util.ArrayList;
public class ArrayListTest2{
public static void main(String[] args){
ArrayList<Integer> al = new ArrayList<Integer>();
al.add(1);
System.out.println(al);
Integer i1 = (Integer)al.get(0);
System.out.println(i1);//(1)
/* 下面的代码编译时报错
* The method add(Integer) in the type ArrayList<Integer> is not applicable for the arguments (String)
* */
//al.add("hello");
//Integer i2 = (Integer)al.get(1);
//System.out.println(i2);
}
}
结果:
[1]
1
2.简单泛型类的定义
一个泛型类(generic class)就是具有一个或多个[b]类型变量(type variables)[/b]的类。
例如:Pair<T>泛型类型
package generic;
public class Pair<T>{
private T first;
private T second;
public Pair(){
this.first = null;
this.second = null;
}
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public void setFirst(T newValue){
this.first = newValue;
}
public T getFirst(){
return this.first;
}
public void setSecond(T newValue){
this.second = newValue;
}
public T getSecond(){
return this.second;
}
}
Pair类引入了一个[b]类型变量T[/b],用尖括号(<>)括起来,并放在类名后面。
[b]泛型类可以有多个类型变量[/b]:
public class Pair<T, U>{...}
[b]类定义中的类型变量可指定方法的返回值以及域和局部变量的类型[/b]:
public T getFirst(){}
praivate T first;
public void setFirst(T newValue){}
注释:类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示集合的元素类型,K和V表示表的关键字与值的类型。T(需要时还可以用临时的字母U和S)表示“任何类型”。
用[b]具体的类型[/b]替换[b]类型变量[/b]就可以[b]实例化泛型类型[/b],例如:
Pair<String>
可以将结果(Pair<String>)想象成带有构造器的普通类:
Pair<String>()
Pair<String>(String, String)
和方法:
String getFirst()
String getSecond()
void setFirst(String)
void setSecond(String)
使用Pair类:
计算静态的minmax方法遍历数组,并计算出最小值和最大值,用一个Pair对象返回这两个结果。
package generic;
public class ArrayAlg{
public static Pair<String> minmax(String[] a){
if(a == null || a.length == 0)
return null;
String min = a[0];
String max = a[0];
for(int i = 1; i < a.length; i++){
if(min.compareTo(a[i]) > 0){
min = a[i];
}
if(max.compareTo(a[i]) < 0){
max = a[i];
}
}
return new Pair<String>(min, max);
}
//test
public static void main(String[] args){
String[] words = {"Hello", "World", "very", "good"};
Pair<String> mm = ArrayAlg.minmax(words);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
}
结果:
min = Hello
max = very
3.泛型方法
前面已经介绍了如何定义一个泛型类,下面介绍定义一个带有类型参数的方法:
public class ArrayAlg{
public static <T> T getMiddle(T[] a){
return a[a.length / 2];
}
}
这是一个泛型方法。
泛型方法的格式:
修饰符 <类型变量1, 类型变量2, ...> 返回值类型 方法名(参数列表){
//方法体
......
}
泛型方法可以定义在普通方法中,也可以定义在泛型类中。
当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
String[] names = {"John", "Q", "Public"};
String middle = ArrayAlg.<String>getMiddle(names);
System.out.println(middle);//Q
在这种情况下(实际也是大多数情况)下,方法调用中可以省略<String>类型参数。编译器有足够的信息能够推断出所调用的方法。它用names的类型(即String[])与泛型类型T[]进行匹配并推断出T一定是String。也就是说,可以调用
String middle = ArrayAlg.getMiddle(names);
大多数情况下,对于泛型方法的类型引用没有问题。偶尔,编译器也会提示错误,此时需要编译错误报告。
4.类型变量的限定(Bounds for Type Variables)
有时候,类或方法需要对类型变量加以约束。
计算数组中最小的元素:
public class ArrayAlg{
public static <T> T min(T[] a){
if(a == null && a.length == 0){
return null;
}
T smallest = a[0];
for(int i = 0; i < a.length; i++){
if(smallest.compareTo(a[i]) > 0){
smallest = a[i];
}
}
return smallest;
}
}
这里存在一个问题。方法min中,变量smallest的类型为T,这意味着其可以是任何一个类型的对象,由此便不能确定T所属的类型是否有compareTo方法。因此此处会提示编译错误:
The method compareTo(T) is undefined for the type T
解决方案:将T限制为实现了Comparable接口(只含一个方法compareTo的标准接口)的类。可以通过对类型变量设置限定(bound)实现这一点:
public static <T extends Comparable> T min(T[] a){...}
实际上Comparable接口本身就是一个泛型类型。目前,我们忽略其复杂性以及编译器产生的警告。
现在,泛型的min方法只能被实现了Comparable接口的类(如String、Date等)的数组调用。
<T extends Bounding Type>
表示T应该是绑定类型(bounding type)的子类型(subtype)。T和绑定类型可以是类,也可以是接口。
一个类型变量或通配符可以有多个限定(multiple bounds),例如
T extrnds Comparable & Serializable
限定类型(bounding types)用“&”分隔,而逗号用来分隔类型变量(type variables)。
在Java的继承中,可以有任意多个接口超类型,但最多只有一个限定是类。如果有一个类作为限定,它必须是限定列表中的第一个。
(As with Java inheritance, you can have as many interface supertypes as you like, but at most one of the bounds can be a class. If you have a class as a bound, it must be the first one in the bounds list. )
例子:重新编写一个泛型方法minmax,该方法计算出泛型数字中的最小值和最大值,并返回Pair<T>。
package generic;
public class ArrayAlg1{
public static <T extends Comparable> Pair<T> minmax(T[] a){
if(a == null || a.length == 0 ){
return null;
}
T min = a[0];
T max = a[0];
for(int i = 0; i < a.length; i++){
if(min.compareTo(a[i]) > 0){
min = a[i];
}
if(max.compareTo(a[i]) < 0){
max = a[i];
}
}
return new Pair<T>(min, max);
}
//test
public static void main(String[] args){
String[] words = {"Hello", "World", "very", "good"};
Pair<String> mm = ArrayAlg1.minmax(words);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
}
5.泛型代码与虚拟机
[b]虚拟机没有泛型类型对象——所有对象都属于普通类。[/b]
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是移除类型参数(type parameters)后的泛型类型名。类型变量(type variables)被擦除(erased),并且被替换为它的限定类型(bounding types)(或者是Object对于无限定的变量)。
例如,Pair<T>的原始类型如下;
public class Pair{
private Object first;
private Object second;
public Pair(Obejct first, Object second){
this.first = first;
this.second = second;
}
public Object getFirst(){
return this.first;
}
public void setFirst(Object newValue){
this.first = first;
}
public Object getSecond(){
return this.second;
}
public void setSecond(Object newValue){
this.second = newValue;
}
}
因为T是一个无限定的变量(unbounded type variable),所以直接使用Object替换。
结果是一个普通的类,就好像泛型引入Java语言之前已经实现的那样。
在程序中可以包含不同类型的Pair,例如,Pair<String>或Pair<Integer>。而擦除类型后就变成原始的Pair类型了。