引入泛型
Integer max(Integer a, Integer b); //整形数据比较 String max(String a, String b); // 字符型数据比较 /***更加简洁的实现***/ T max(T a, T b); // 比较大小返回更大的那个对象
我们知道有许多的处理过程或者处理模式与具体的数据关系不大。像上面对于整形数据和字符型数据大小的比较如果我们使用重载的方式来实现不同数据类型的max(),这种含有大量雷同代码的设计方式会让软件工程师们设计和维护起来比较麻烦。出于这个问题,我们使用泛型来简单的解决它。
注意:也许你会想:用Object来刻画数据不就行了?
行吗,转换一下就是 Object max(Object a, Object b); 这样可以填充数据了,但是我们要求a,b所属的类型要有可比性,假如a是Integer型b是String型也是可以填充的,但他们没有可比性将会导致运行时错误。
那为什么泛型就可以呢?来讲讲泛型的功能。
泛型的功能
泛型:是一种能表达能够表达更广泛数据的类型,注意咯,泛型变量的值是一种类型,而非类型取值范围内的值。
泛型机制是一种特殊的类型机制,有两方面的功能:
1. 通用型功能:泛型机制提供一种特殊的带参数描述方式(特殊在于:以类型为参数) 比如上面:T是形式参数,T可以取值Integer、String这些类型。
2. 类型检查功能:说到泛型的类型检查,这就是为什么上面那个例子可以用泛型而不能用Object的原因了。借助编译器当你传入的数据a为整形数据而b为字符串数据时编译阶段会报错。
注意:泛型的检查是在编译阶段的,对于这点要谨记。在java的泛型机制中,必须在编译阶段确知填入的具体类型,不支持在程序的运行阶段传入类型参数。普通的参数都是在运行时传入的,但泛型参数必须在编译时确定。
泛型的申明
class 类名 <类型参数列表>{类体}
如: class A<T1, T2> {T1 a; T2 b;} // 在类体中,你把T1,T2当做已知类来使用就好了
泛型的具体化
类名 <具体类型列表> 变量=new 类名<具体类型列表>{构造函数的参数列表} // 注意:具体类型必须是引用型类型
如:A<Integer, String> x=new A<Integer, String>();
泛型参数可以出现在什么地方
泛型参数可以出现在类、接口、成员方法、内嵌类、内嵌接口中
值得注意的是:泛型方法不仅可以出现在泛型类中,也可以出现在普通类中。不过,当泛型方法出现在普通类中时,记得要在泛型方法中申明泛型参数。 如:
<T> void function(T a){……}
泛型约束和泛型通配符
泛型约束:<T extends BoundingType> // BoundingType 可以是类,也可以是接口;可以有多个BoundingType,用&隔开;但只能有一个类并必须放在第一位置
表示 T必须是BoundingType的子类型(具体化时确定了),或者是实现了BoundingType这个接口的类型
如:<T extends Number> // 要求T的值是Number的子类,且不能是Number类型
<T extends Comparable> // 要求T的值(类型)是个实现了Comparable接口的类
泛型中的通配符:<? extends T> // T即可以是类也可以是接口,把?理解为类型参数,T是类型的上界;?必须是T的子类
来比较一下: LinkList<Number> // 以具体化,说明LinkList类中的数据必须是Number类型或者Number的子类型
LinList<T extends Number> // T标识的是一种确定的类型参数,需要要具体化的
LinkList<? extends Number> // ?标识的是一种不确定的类型参数,不要具体化的
来验证一下LinkList<Number>的LinkList类中可以是Number的子类型:
public class Test { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub TestClass<Number> test=new TestClass<Number>(3); TestClass<Number> test1=new TestClass<Number>(2.33); //TestClass<Number> test=new TestClass<Integer>(3); 注意:编译报错 System.out.println(test.getData()); System.out.println(test1.getData()); } } class TestClass<T>{ private T date; TestClass(T data){ this.date=data; } public void setData(T data){ this.date=data; } public T getData(){ return this.date; } } 输出: 3 2.33
这样看起来感觉后面两个差不多,其实是非常有区别的,举个列子老看下:
// 一个链表,节点中的数据必须是可比较的 import java.util.Scanner; public class Ch_11_3 { /** * @param args */ public static void main(String[] args) { // 整形数据链表 System.out.println("创建整数链表,以-1结尾:"); Scanner in=new Scanner(System.in); LinkList<Integer> int_list=new LinkList<Integer>(); int x=in.nextInt(); while(x!=-1){ int_list.addNode(x); x=in.nextInt(); } int_list.printList(); System.out.println(); // String类型数据链表 System.out.println("输入一组String,以空格为分隔符:"); LinkList<String> string_list=new LinkList<String>(); Scanner in_2=new Scanner(System.in); String str=in_2.nextLine(); String[] str_array=str.split(" "); for(String s : str_array){ string_list.addNode(s); } string_list.printList(); } } class LinkList<T extends Comparable>{//Comparable 是个接口 <T extends BoundingType> Bounding可以是个类也可以是个接口 // 定义节点类 class Node{ private T data; //节点存储的数据 private Node next; //指向后一节点的指针 public Node(T x){ // 构造函数 this.data=x; } public Node getNext(){ //获取节点的后一个节点 return this.next; } public void setNext(Node p){ //设置指针的指向元素 this.next=p; } public T getData(){ //获取节点元素 return this.data; } public void setData(T d){ //设置节点元素 this.data=d; } } private Node head,tail; // 头指针和尾指针 public LinkList(){ head=tail=null; } public boolean isEmpty(){ //判断是否为空链表 return head==null; } public Node getHead(){ //获取表头 return head; } private T max(T a, T b){ //比较两个节点的元素大小 return (a.compareTo(b)>0)?a:b; } public T getMaxValue(){ //获取链表中的最大值 if(isEmpty()){ return null; } T max_value=this.head.getData(); Node p=this.head.getNext(); while(p!=null){ max_value=max(p.getData(),max_value); p=p.next; } return max_value; } public void printList(){ Node p=head; while(p!=null){ System.out.print(p.getData()+"=="); p=p.getNext(); } } public void addNode(T x){ Node p=new Node(x); if(isEmpty()){ head=tail=p; } else{ tail.setNext(p); tail=p; } } }
假如我现在要求添加一个sum()方法:用于计算节点中数据的和应该怎么办?
首先,要求节点数据要可以累加,说明节点数据是数值型,那么如何实现呢?
/* public double sum(LinkList<Number> ls){ return 0; }//Bound mismatch: The type Number is not a valid substitute for the bounded parameter <T extends Comparable> of the type LinkList<T> // 意思说Number不是一个可比较的类型 */ public double sum(LinkList<? extends Number> ls){ return 0; }// 编译通过
泛型中的通配符是泛型部分最难的,我也没有完全理解关于通配符的细节可以参考IBM developerworks中的一篇文章:http://www.ibm.com/developerworks/cn/java/j-jtp04298.html
泛型机制原理(很重要)
我总结了几点:
1, JVM不支持泛型
2, 泛型实现机理:擦拭(所谓擦拭,就是编译器对泛型类/接口在编译前会对其实施转换,将其转换成普通类/接口)
对于泛型擦拭机理的学习我建议通过反编译字节码文件的方式来学习(反编译得到了的源码是擦拭后的,没有泛型):~$ javap 类名
意错点
1, T t=new T(); // 编译错误,不能创建泛型对象
2, T<String> t1=new T<String>();
T<Integer> t2=new T<Integer>();
t1=t2;
// 编译错
3, class A extends <T> {}
class B implements <T> {}
// 编译错误 , 因为将会破坏java单一继承原则
4, 重载方法
class A<T1, T2>{
public void function(T1 t1){;}
public void function(T2 t2){;}
}
// 编译错误,假如T1,T2都是String,拿着两个函数就完全相同了
java 泛型部分还蛮难的,看了很多资料,有些东西还真是不好怎么表达,如果你有什么疑问或者建议欢迎提出一起探讨