目录
2.6 Class<T>
2、泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型的用法:
泛型可以用以下几种形式:
- 用在类上 class Test<E>
- 用在方法中 <U> returnType m( U o)
2.1 泛型方法
定义泛型方法的规则
- 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的<E>)。
- 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
- 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
- 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
泛型方法的例子:
public class GenericMethodTest
{
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
public static void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组
System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组
System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}
类型变量的限定
有时,类或方法要对类型变量加以约束。看下面一个例子:
//下面是比较最小值的方法
public static <T> T min(T[] a){
if(a==null||a.length==0) return null;
T small =a[0];
for(T t:a){
if(small.compareTo(t)>0) small=t;
}
return small;
}
但是上面的方法是不对的,因为并没有指定类型变量T所属的类就有CompareTo方法,所以要将类型变量T的限定实现Comparable,下面的方法才是正确的
public static <T extends Comparable> T min(T[] a){
if(a==null||a.length==0) return null;
T small =a[0];
for(T t:a){
if(small.compareTo(t)>0) small=t;
}
return small;
}
2.2 泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
下面使用了两个泛型的类:在定义类时,不能确定参数的类型或者类
import java.util.ArrayList;
public class Model <E,T> {
private E e;
private T t;
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public static void main(String[] args) {
Model<String,Object> model = new Model<String, Object>();
model.setE("mm");
model.setT(new ArrayList<String>());
}
}
2.3 泛型接口
泛型接口的定义格式:
interface 接口名<声明自定义泛型>{}
泛型接口要注意的事项
- 接口上自定义的泛型的具体数据类型是在实现一个接口的时候指定的。
- 在接口上自定义的泛型如果在实现接口的时候没有指定具体的数据类型,那么默认为Object类型
- 目前实现一个接口的时候,还不明确目前要操作的数据类型,要等到创建接口实现类对象的时候才去指定泛型的具体数据类型如果要延长接口自定义泛型 的具体数据类型,那么格式如下:
修饰符 class 类名<声明自定义泛型> implements 接口名<声明自定义泛型>{}
2.4 泛型的约束与局限
下面讲解使用Java泛型时需要考虑的一些限制,大多数限制都是由于类型擦除引起的。
类型擦除:
Java泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:
Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类型在编译后都会被清除掉,因为虚拟机不支持泛型,将类型变量换成限定类型(无限定变量转换成Object)
2.4.1 不能用基本类型实例化类型参数
不能用类型参数代替基本类型,因此没有Pair<double>,只有Pair<Double>.原因就是类型擦除后Pair类中含有Object的域,而Object不能存储double。
2.4.2 运行时类型查询只适用于原始类型
所有类型的查询只是与原始类型进行对比,不能加入类型变量。
if(a instanceOf Pair<String>)//错误使用
if(a instanceOf Pair)//正确写法
同理,getClass方法也只是返回原始类型:
Pair<String> strPair=..;
Pair<Employee> empPair=..;
if(strPair.getClass()==empPair.getClass())//结果true
2.4.3 不能创建参数化类型的数组
Pair<String>[] table = new Pair<String>[10];//错误写法
类型擦出后,变成了table的类型为Pair[],可以将其转换成Object[]
Pair<String>[] table = new Pair<String>[10]
Object[] obj = table;
obj[0] = "hello";//编译抛出异常
综上:可以定义 Pair<String>[] table,但是不要使用new Pair<String>[10]初始化变量。
2.4.4 不能实例化类型变量
不能使用new T(),new T[],T.class这样的表达式的类型变量。
2.4.5 泛型类的静态上下文中类型变量无效
不能在静态域或者方法中引用类型变量。
public static T getInstance(){}//错误写法
2.4.6 不能抛出或捕捉泛型类的实例
既不能抛出也不能捕捉泛型类对象。但是在处理异常的方法中可以使用类型变量。
try{
}catch(Exception e){
t.methods();
}
2.5 泛型类型继承规则
使用泛型类时,我们有必要了解一些泛型类之间有关继承和子类型的准则。相同的类中包含不同的泛型,就代表着没有什么联系,例如MallSK<T>和MasllSK<S>,这个T和S之间即便有继承关系,这两个类的对象也不能相互转换。
泛型与数组不同,数组中将一个子类的数组存放到一个父类的数组中,也就是Manager[]数组赋值给Employee[]数组,当Manager数组的存放值改变时,如果将一个低级别的雇员放在高级别的雇员位置,虚拟机会抛出ArrayStoryException异常。
2.6 Class<T>
class<T>中的T代表Class类的类型。例如,String.class
的类型是 Class<String>
。使用Class<T>作为返回值的好处是可以确定返回类型,而不是使用Object为返回类型,使用Object作为返回类型的缺点是容易将类的类型转错。