首页 > 代码库 > 十:Java之泛型
十:Java之泛型
【定义】
一、泛型的定义主要有以下两种:
在程序编码中一些包含类型参数的类型,也就是说泛型的参数只可以代表类,不能代表个别对象。(这是当今较常见的定义)
在程序编码中一些包含参数的类。其参数可以代表类或对象等等。(现在人们大多把这称作模板)
不论使用那个定义,泛型的参数在真正使用泛型时都必须作出指明。
泛型类,是在实例化类的时候指明泛型的具体类型;
泛型方法,是在调用方法的时候指明泛型的具体类型。
二、使用泛型的目的:
一些强类型程序语言支持泛型,其主要目的是加强类型安全及减少类转换的次数,但一些支持泛型的程序语言只能达到部份目的。
泛型程序设计(Genericprogramming)意味着编写的代码可以被很多不同类型的对象所重用。
是对java语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值得占位符一样。
【Java泛型的几种类型代码】
一、不使用泛型的代码
我们定义一个Person类,包含三个属性x,y,z。在我们开始定义地时候,我们也不知道这三个属性是用来干什么的,所以我们定义为Object类型。但是在使用的时候,我们分别对x,y,z赋予了int,double,String类型,所以在取出的时候,我们需要把这三个类型值进行强制转换。如下代码:
1. Person.java
<span style="font-size:18px;"> public class Person { private Object x; private Object y; private Object z; //使用Object类型。可以转化为任何类型 public Object getX() { return x; } public void setX(Object x) { this.x = x; } public Object getY() { return y; } public void setY(Object y) { this.y = y; } public Object getZ() { return z; } public void setZ(Object z) { this.z = z; } } 2. NoGenericTest.java public class NoGenericTest { public static void main(String[]args){ Person boy=new Person(); boy.setX(20); boy.setY(22.2); boy.setZ("帅哥TT"); //这里根据设置的不同类型的值,我们需要进行强制类型转化。 int x=(Integer)boy.getX(); double y=(double)boy.getY(); String z=(String)boy.getZ(); System.out.println(x); System.out.println(y); System.out.println(z); } } 3. 运行结果 20 22.2 帅哥TT</span>
二、使用一个类型变量泛型的代码
我们定义一个泛型类Person,定义三个属性x,y,z,在测试类中,我们设置属性的值,并打印。
<span style="font-size:18px;"> 1. Person.java public class Person<T> { private T x; private T y; private T z; public T getX() { return x; } public void setX(T x) { this.x = x; } public T getY() { return y; } public void setY(T y) { this.y = y; } public T getZ() { return z; } public void setZ(T z) { this.z = z; } } 2. GenericTest.java public class GenericTest { public static void main(String[]args){ Person boy=new Person(); boy.setX(20); boy.setY(22.2); boy.setZ("帅哥TT"); //不用进行类型转化 System.out.println(boy.getX()); System.out.println(boy.getY()); System.out.println(boy.getZ()); } } 3. 运行结果 20 22.2 帅哥TT 三、使用两个类型变量泛型的代码 我们定义一个泛型类Person,定义两个属性x,y,使用了两种不同的类型变量,在测试类中,我们设置属性的值,并打印。 1. Person.java public class Person<T1,T2> { private T1 x; private T2 y; public T1 getX() { return x; } public void setX(T1 x) { this.x = x; } public T2 getY() { return y; } public void setY(T2 y) { this.y = y; } } 2. GenericTest.java public class GenerricTest { public static void main(String[] args){ Person<String,Integer> boy=new Person<String,Integer>(); boy.setX("帅哥TT"); boy.setY(20); System.out.println(boy.getX()); System.out.println(boy.getY()); } } 3. 运行结果 帅哥TT 20</span>
四、使用泛型的继承
我们定义一个泛型类Person,定义两个属性x,y,然后定义另一个泛型类Boy,定义属性z,Boy继承Person类,在测试类中,我们设置属性的值,并打印。
1. Person.java public class Person<T1,T2> { private T1 x; private T2 y; public T1 getX() { return x; } public void setX(T1 x) { this.x = x; } public T2 getY() { return y; } public void setY(T2 y) { this.y = y; } } 2. Boy public class Boy<T1,T2,T3>extendsPerson<T1,T2> { private T3 z; public T3 getZ() { return z; } public void setZ(T3 z) { this.z = z; } } 3. GenericTest.java 1public class GenericTest { 2 public static void main(String[] args){ 3 Boy<String,Integer,Double> boy=new Boy<String,Integer,Double>(); 4 boy.setX("帅哥TT"); 5 boy.setY(20); 6 boy.setZ(200000.22); 7 8 System.out.println(boy.getX()); 9 System.out.println(boy.getY()); 10 System.out.println(boy.getZ()); 11 } 12 } 4.运行结果 1 帅哥TT 2 20 3 200000.22
五、使用泛型的接口
我们定义一个泛型接口Person,定义两个方法,然后定义另一个泛型类Boy,实现泛型接口Person,定义属性x,y,z,在测试类中,我们设置属性的值,并打印。
<span style="font-size:18px;">1. Person.java 1 public interface Person<T1,T2> { 2 public T1 getX(); 3 public T2 getY(); 4 } 2. Boy 1public class Boy<T1,T2,T3>implements Person<T1,T2> { 2 private T1 x; 3 private T2 y; 4 private T3 z; 5 public T1 getX() { 6 return x; 7 } 8 public void setX(T1 x) { 9 this.x = x; 10 } 11 public T2 getY() { 12 return y; 13 } 14 public void setY(T2 y) { 15 this.y = y; 16 } 17 public T3 getZ() { 18 return z; 19 } 20 public void setZ(T3 z) { 21 this.z = z; 22 } 23 24 } 3. GenericTest.java 1public class GenericTest { 2 public static void main(String[] args){ 3 Boy<String,Integer,Double> boy=newBoy<String,Integer,Double>(); 4 boy.setX("帅哥TT"); 5 boy.setY(20); 6 boy.setZ(200000.22); 7 System.out.println(boy.getX()); 8 System.out.println(boy.getY()); 9 System.out.println(boy.getZ()); 10 } 11 } 4. 运行结果 1 帅哥TT 2 20 3 200000.22</span>
六、使用泛型方法
说明一下,定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。
定义一个普通类Person,定义一个泛型方法,如下代码:
<span style="font-size:18px;">1. Person.java 1public class Person{ 2 public static<T>T getMiddle(T[]a){ 3 return a[a.length/2]; 4 } 5 public static void main(String [] args){ 6 String[]name={"帅哥TT","帅哥TT1","帅哥TT2"}; 7 String middle=Person.<String>getMiddle(name); 8 System.out.println(middle); 9 10 Integer[]num={20,22,25}; 11 Integer middle1=Person.<Integer>getMiddle(num); 12 System.out.println(middle1); 13 14 Double[]num1={20.0,22.2,25.5}; 15 Double middle2=Person.<Double>getMiddle(num1); 16 System.out.println(middle2); 17 } 18 } 2. 运行结果 1 帅哥TT1 2 22 3 22.2</span>
七、类型变量的限定
如下代码,我们在方法min中定义了一个变量smallest类型为T,这说明了smallest可以是任何一个类的对象,我们在下面的代码中需要使用compareTo方法, 但是我们没有办法确定我们的T中含有CompareTo方法,所以我们需要对T进行限定,在代码中我们让T继承Comparable类。如下:
<span style="font-size:18px;">public static<T extendsComparable>T min(T[]a) 1.Person.java 1public class Person{ 2 public static<T extends Comparable>T min(T[]a){ 3 if(a==null||a.length==0){ 4 return null; 5 } 6 T smallest=a[0]; 7 for(int i=1;i<a.length;i++){ 8 if(smallest.compareTo(a[i])>0){ 9 smallest=a[i]; 10 } 11 } 12 return smallest; 13 } 14 public static void main(String [] args){ 15 Integer[]num={20,25,30,10}; 16 Integer middle=Person.<Integer>min(num); 17 System.out.println(middle); 18 } 19 } 2. 运行结果 10</span>
【Java泛型理解】
一、类型擦除
正确理解泛型概念的首要前提是理解类型擦除(type erasure)。 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。
很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:
泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。
静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。
泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>和MyException<Integer>的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。
二、最佳实践
在使用泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。
在代码中避免泛型类和原始类型的混用。比如List<String>和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。当需要利用JDK 5之前开发的遗留代码,而不得不这么做时,也尽可能的隔离相关的代码。
在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。
泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List<String>[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。
不要忽视编译器给出的警告信息。