Java 泛型
1 什么是泛型 ........................................................................................................................... 2
2 泛型类跟接口及泛型方法 ................................................................................................... 3
2.1 泛型类跟接口及继承 ................................................................................................ 3
2.1.1泛型类 .............................................................................................................. 3
2.1.2继承 .................................................................................................................. 3
2.1.3接口 .................................................................................................................. 3
2.2 泛型方法 .................................................................................................................... 3
2.2.1 方法 ................................................................................................................. 3
2.2.2 类型推断 ......................................................................................................... 4
3 泛型实现原理 ....................................................................................................................... 5
4 泛型数组 ............................................................................................................................. 6
5 边界 ........................................................................................................................................ 7
6 通配符 .................................................................................................................................... 8
7 泛型的问题及建议 ............................................................................................................... 9
7.1 问题 ............................................................................................................................. 9
7.2 建议 ............................................................................................................................ 9
1 什么是泛型
从jdk1.5 开始,Java 中开始支持泛型了。泛型是一个很有用的编程工具,给我们带来了
极大的灵活性。在看了《java 核心编程》之后,我小有收获,写出来与大家分享。
所谓泛型,我的感觉就是,不用考虑对象的具体类型,就可以对对象进行一定的操
作,对任何对象都能进行同样的操作。这就是灵活性之所在。但是,正是因为没有考虑
对象的具体类型,因此一般情况下不可以使用对象自带的接口函数,因为不同的对象所
携带的接口函数不一样,你使用了对象A的接口函数,万一别人将一个对象B传给泛型,
那么程序就会出现错误,这就是泛型的局限性。所以说,泛型的最佳用途,就是用于实
现容器类,实现一个通用的容器。该容器可以存储对象,也可以取出对象,而不用考虑
对象的具体类型。因此,在学习泛型的时候,一定要了解这一点,你不能指望泛型是万
能的,要充分考虑到泛型的局限性。下面我们来探讨一下泛型的原理以及高级应用。首
先给出一个泛型类:
public class Pair<T>
{
public Pair() { first = null; second = null; }
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
private T first;
private T second;
}
我们看到,上述 Pair类是一个容器类(我会多次强调,泛型天生就是为了容器类的
方便实现),容纳了2 个数据,但这2 个数据类型是不确定的,用泛型T 来表示。关于
泛型类如何使用,那是最基本的内容,在此就不讨论了。 2 泛型类跟接口及泛型方法
2.1 泛型类跟接口及继承
2.1.1泛型类
泛型可以继承自某一个父类,或者实现某个接口,或者同时继承父类并且实现接口
泛型也可用于匿名内部类
匿名内部类
2.1.2继承
泛型类也可以继承
Public Class A<T,A> extends
Public Class A<T,A,B> extends ,
2.1.3接口
泛型也可以用于接口
Public interface A<T>{}
2.2 泛型方法
2.2.1 方法
是否拥有泛型方法跟其所在的类是否是泛型没有关系。原则:无论何时只要你能做
到,你就尽可能的使用泛型方法。对于 static 方法你只能使用泛型方法。 在调用泛型方法时,通常不必指明参数类型 因为编译器会为我找出具体类型,这
称之为类型推断。
泛型方法也能跟可变参数共存: public static <T> void f(T…arg){}
2.2.2 类型推断 编译器能根据 推断出 返回的类型
类型推断只能对赋值语句有效: 这句不能被编译
在范型方法中你可以显示指明类型,要指明类型必须在 点操作符与方法名之间插入尖
括号,然后把类型置于尖括号中,如果是在类中的方法中使用必须加this.<type> 如果是
static方法需要加上类名。
3 泛型实现原理
下面我们来讨论一下 Java 中泛型类的实现原理。在 java中,泛型是在编译器中实
现的,而不是在虚拟机中实现的,虚拟机对泛型一无所知。因此,编译器一定要把泛型
类修改为普通类,才能够在虚拟机中执行。在 java中,这种技术称之为“擦除”,也就是
用Object 类型替换泛型。上述代码经过擦除后就变成如下形式:
public class Pair
{
public Pair(Object first, Object second)
{
this.first = first;
this.second = second;
}
public Object getFirst() { return first; }
public Object getSecond() { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
private Object first;
private Object second;
}
大家可以看到,这是一个普通类,所有的泛型都被替换为Object类型,他被称
之为原生类。每当你用一个具体类去实例化该泛型时,编译器都会在原生类的基础上,
通过强制约束和在需要的地方添加强制转换代码来满足需求,但是不会生成更多的具体
的类(这一点和c++完全不同)。我们来举例说明这一点:
Pair<Employee> buddies = new Pair<Employee>();
//在上述原生代码中,此处参数类型是Object,理论上可以接纳各种类型,但编译器
通过强制约束
//你只能在此使用 Employee(及子类)类型的参数,其他类型编译器一律报错
buddies.setFirst(new Employee("张三"));
//在上述原生代码中,getFirst()的返回值是一个Object类型,是不可以直接赋给类型
为Employee的 buddy的
//但编译器在此做了手脚,添加了强制转化代码,实际代码应该是
Employee buddy = (Employee)buddies.getFirst();
//这样就合法了。但编译器做过手脚的代码你是看不到的,他是以字节码的形式完成
的。
Employee buddy = buddies.getFirst();
4 泛型数组
你不能这样创建泛型数组 T[] array = new T[size]; 因为编译器无法确定 T 实际类型
ArrayList<String>[] array = new ArrayList<String>[ size]; 这条语句同样也是不能编译的
原因是数组将将跟踪它们的实际类型,而这个类型是在数组创建时确定的。然而由于擦
除泛型的类型信息被移除了,数组就无法保证 ArrayList的实际类型.
但是你可以这样 ArrayList<String>[] array = (ArrayList<String>[]) new ArrayList[1] 编译器确
保 你只能向数组中添加ArrayList<String>() 对象而不能添加 ArrayList<Integer>等其他类
型。
创建泛型数组的二种方式: 第一种:
只能用 数组接受 因为泛型数组在运行时只能是
第二种:
该数组运行时类型是确切的类型
5边界
泛型就像字面意思表达的那样,你可以将代码运用到任何类型 既然是任何类型那
么你就不能在类型上调用任何方法。但是Object中的方法是可以的,因为编译器知道
这个类型至少是一个Object,任何类就直接或者间接继承了 Object 类。如果什么方法
都不能调那么你能用来干什么能,所以如果你想要调用方法那么你就需要边界就像下面
这表示 只要是 和任何它的子类都可以。那么现在
你可以调用getColor()方法了,因为编译器知道T至少是 HasColor 类型,擦除也会擦
除到边界这里是 HasColor 如果没有设定边界那么默认会擦除到Object。 如何你想要限
定多个边界那么你只需在后面加上&符号即可。
6通配符
数组的协变:如果 B 是 A的子类那么 A[] = new B[size] 是可以的。
Number[] num = new Integer[size] 那么如果向 num 数组中加入 Double 类型呢?(既然
是Number类型那么你就没有理由阻止放入 Number 类型)你向Integer数组中加入
Double这肯定导致错误,数组是 Java 内置类型他能在编译期跟运行进行检查错误。所
以我们无需担心。
那么我们来看看这条语句 ArrayList<Number> arr = new ArrayList<Integer>();
这条语句不能编译,原因是因为泛型不能协变,如果你允许的话 那么你就可以往里面
加入 Number 子类如 Double 而你引用的却是 ArrayList<Integer>类型,这违反了泛型的
初衷编译期类型安全。所以如果我们要想实现向数组那样协变那么就要用到通配符,<?
extends Number> 表示我不知道是什么类型为了协办我也不关心。它只要是 Number
的子类或者Number 就行。 我们知道即使你向上转型了(协变),你也不能保证类型安全。
所以泛型向上转型之后你就丢失了向其中添加任何东西的能力,即使 Ojbect 也不行.但
是添加null是可以的。因为null是任何对象的引用但没有实际意义。 就是你即使往Numer
数组中添加 null 一样它并不会报错。Get()方法能够能行是编译器至少知道?至少是一个
Number
public static void test(ArrayList<? extends Number> arr) {
// arr.add(new Integer(2)); 不能编译
arr.add(null);//OK
Number obj = arr.get(0);
}
如果你想向其中添加内容那么你可以使用<? Supper Number >表示我不知道是什么类
型为了协办我也不关心。它只要是Number或者 Number的子类就行,add()方法只能
添加Number 或者Number 的子类。为什么现在添加内容是安全的呢?因为编译器至少知道Number 类型或者 Number的父类型,所以向里添加子类是安全的 如:
ArrayList<Object> 你可以添加任何类型。这当然包括 Number 或者其子类啦,但你却
不能再添加其他的类型了 Number的父类(Object)也不行,如果可以的那么就可添加非
Number 类型了。然而 Number obj = arr.get(0);不能正常运行了。因为这时编译器只知
道是Object 类型
public static void test(ArrayList<? supper Number> arr) {
arr.add(new Number());//OK
arr.add(new Integer(2)); OK
arr.add(null);//OK
!//Number obj = arr.get(0);//无法判断是否是 Number 类型
Object obj = arr.get(0)
}
7 泛型的问题及建议
7.1 问题
1 任何基本类型不能作为泛型参数
2 一个不能实现同一个泛型接口 原因:擦除
3 catch 语句不能捕获泛型异常
7.2 建议
以上就是我学习泛型的所有心得。下面再把《Java核心编程》中列出的使用泛
型时的注意事项列出来(各种操作被禁止的原因就不具体说明了),供大家参考:
//1、不可以用一个本地类型(如 int float)来替换泛型
//2、运行时类型检查,不同类型的泛型类是等价的(Pair<String>与Pair<Employee>
是属于同一个类型Pair),
// 这一点要特别注意,即如果 a instanceof Pair<String>==true 的话,并不代表
a.getFirst()的返回值是一个 String 类型 //3、泛型类不可以继承 Exception类,即泛型类不可以作为异常被抛出
//4、不可以定义泛型数组
//5、不可以用泛型构造对象,即first = new T(); 是错误的
//6、在static方法中不可以使用泛型,泛型变量也不可以用static 关键字来修饰
//7、不要在泛型类中定义 equals(T x)这类方法,因为 Object 类中也有 equals 方法,
当泛型类被擦除后,这两个方法会冲突
//8、根据同一个泛型类衍生出来的多个类之间没有任何关系,不可以互相赋值
// 即 Pair<Number> p1; Pair<Integer> p2; p1=p2; 这种赋值是错误的。
//9、若某个泛型类还有同名的非泛型类,不要混合使用,坚持使用泛型类
// Pair<Manager> managerBuddies = new Pair<Manager>(ceo, cfo);
// Pair rawBuddies = managerBuddies; 这里编译器不会报错,但存在着严重的运
行时错误隐患