1. 为什么使用泛型程序设计? 使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。 泛型对于集合类尤其有用。
泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所 重用。
2. 泛型定义 声明类的泛型版本与声明类的一般版本的语法格式很类似,只有稍微的不同。
声明一般类的语法格式为:
[修饰符] class 类名 [extends 父类名] [implements 接口1 名,接口2 名,…]{ }
而声明泛型类型时,只需要在类名后面跟上一个类型参数即可,其语法格式为:
[修饰符] class 类名<类型参数> [extends 父类名] [implements 接口1 名,接口2 名,。。。]{ }
[修饰符] class 类名<类型参数1, 类型参数2, 类型参数3 > [extends 父类名] [implements 接口1 名,接口2 名,。。。]{ }
每个类型参数必须唯一。
代码示例:
public class Box { private Object object; public void add (Object object) { this .object = object; } public Object get () { return object; } }
public class Box <T> { private T t; public void add (T t) { this .t = t; } public T get () { return t; } }
3. 泛型使用 一个泛型类型调用通常被看做是“参数化类型”。要实例化这个类,照常使用new关键字,但是将<Integer>
放在类名和圆括号之间:
Box<Integer> integerBox = new Box <Integer>(); integerBox.add(new Integer (10 ));Integer someInteger = integerBox.get(); System.out.println(someInteger)
4.泛型方法 4.1 定义泛型方法 类型参数还可以在方法和构造器签名中声明,用来创建“泛型方法”和“泛型构造器”。这与声明一个泛型类型相似,但是类型参数的作用域被限制在它被声明的方法或构造器中。
声明格式:
[修饰符] <类型变量> [返回类型] 方法名(参数列表){ }
代码示例:
class ArrayAlg { public static <T> T getMiddle (T... a) { return a[a.length / 2 ]; } }
这个方法是在普通类中定义的, 而不是在泛型类中定义的。 然而, 这是一个泛型方法, 可以从尖括号和类型变量看出这一点。注意:类型变量放在修饰符(这里是 public static) 的后面,返回类型的前面。
4.2 调用泛型方法 当调用一个泛型方法时’ 在方法名前的尖括号中放入具体的类型
调用ArrayAlg类中的泛型方法:
String middle = ArrayAlg.<String >getMiddle ("]ohnM, " Q.n, "Public" );
在大多数情况下,方法调用中可以省略类型参数。编译器有足够的信息能够推断出所调用的方法。它用 names 的类型(即 String[]) 与泛型类型 T[ ] 进行匹配并推断出 T 一定是 String。也就是说上述代码也可以修改为:
String middle = ArrayAlg.getHiddle("]ohn" , "Q." , "Public" );
5.类型变量 5.1 类型参数命名惯例 按惯例,类型参数命名为单个的大写字母。这与已经知道的变量命名规则不太相同。这样命名的原因是:如果不这样命名,将很难区分在一个类型变量和一个变通类或接口名称之间的不同。最普遍使用的类型参数名称如下:
E——元素(被Java集合框架所广泛地使用)。
K——键。
N——数字。
T——类型。
V——值。
S,U,V等——第二、第三、第四个类型。
5.2 类型变量的限定 5.2.1 类型参数边界 默认可以使用任何类型来实例化一个泛型类对象,但Java中也对泛型类实例的类型作了限制。
语法如下:
anyClass指某个接口或类
anyClass指的是上限
限定上限的类型中,最多只能有1个类(或0个类),其余的必须均为接口。
限定上限的类型中,接口可以有多个,中间用&字符分割。
使用泛型限制后,泛型类的类型必须实现或者继承了anyClass 这个接口或类。无论anyClass是接口还是类,在进行泛型限制时都必须使用extends关键字。
import java.util.List;public class LimitClass <T extends List >{ }
import java.util.List;public class LimitClass { public static <T extends List > void testMedthod (T t) { } }
对类型参数的限定,也可以包括指定额外的必须被实现的接口,使用&字符,例如指定类型变量U可接收的值为实现了MyInterface接口的Number类或其之类。语法如下:
<U extends Number & MyInterface > <U extends Number & MyInterface1 & MyInterface2 & ... &MyInterface ...>
5.2.2 使用通配符: ? 固定的泛型类型使用起来不灵活,java提供一种巧妙的方案:通配符类型。
定义格式
AnyGeneric <?>AnyGeneric <?,?>AnyGeneric <? extends MyClass >
表示任何泛型AnyGeneric类型,它的类型参数是MyClass的子类
代码1:
public class Demo { public static void main (String[] args) { Point<Integer, Integer> p1 = new Point <Integer, Integer>(); p1.setX(10 ); p1.setY(20 ); printPoint(p1); Point<String, String> p2 = new Point <String, String>(); p2.setX("东经180度" ); p2.setY("北纬210度" ); printPoint(p2); } public static void printPoint (Point<?, ?> p) { System.out.println("This point is: " + p.getX() + ", " + p.getY()); } }class Point <T1, T2>{ T1 x; 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:
public class Demo { public static void main (String[] args) { Point<Integer, Integer> p1 = new Point <Integer, Integer>(); p1.setX(10 ); p1.setY(20 ); printNumPoint(p1); Point<String, String> p2 = new Point <String, String>(); p2.setX("东经180度" ); p2.setY("北纬210度" ); printStrPoint(p2); } public static void printNumPoint (Point<? extends Number, ? extends Number> p) { System.out.println("x: " + p.getX() + ", y: " + p.getY()); } public static void printStrPoint (Point<? extends String, ? extends String> p) { System.out.println("GPS: " + p.getX() + "," + p.getY()); } }class Point <T1, T2>{ T1 x; 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; } }
6.类型擦除 当一个泛型被实例化时,编译器通过称为“类型擦除”的技术来编译这些类型。所谓类型擦除,指的是编译器移除一个类或方法中所有与类型参数相关的信息。 类型擦除能够使使用泛型的Java应用程序与Java类库和在泛型出现之前创建的应用程序保持二进制上的兼容性。
例如:将泛型类Pair 进行类型擦除擦除前:
public class Pair <T> { private T first; private T last; public Pair (T first, T last) { this .first = first; this .last = last; } public T getFirst () { return first; } public T getLast () { return last; } }
擦除后:
public class Pair { private Object first; private Object last; public Pair (Object first, Object last) { this .first = first; this .last = last; } public Object getFirst () { return first; } public Object getLast () { return last; } }
因此,Java使用擦拭法实现泛型,导致了:
编译器把类型视为Object;
编译器根据实现安全的强制转型。
7.泛型的局限 Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。了解了Java泛型的实现方式——擦拭法,我们就知道了Java泛型的局限:
7.1 不能用类型参数代替基本类型 不能用类型参数代替基本类型,因此没有 Pair、Pair…
Pair<int > p = new Pair <int >(1 , 2 );
7.2 无法判断带泛型的Class Pair <Integer > p = new Pair <>(123 , 456 );if (p instanceof Pair <String >.class) { }
原因和前面一样,并不存在Pair.class,而是只有唯一的Pair.class。
7.3 无法实例化T类型 public class Pair <T> { private T first; private T last; public Pair () { first = new T (); last = new T (); } }
上述代码无法通过编译,因为构造方法的两行语句:
first = new T (); last = new T (); first = new Object (); last = new Object ();
7.4 无法创建参数化类型的数组 Pair<String >[] table = new Pair <String >[10 ];
需要说明的是,只是不允许创建这些数组,而声明类型为Pair[]的变量仍是合法的,不过不能用new Pair[10]初始化这个变量。
7.5 总结 Java的泛型是采用擦拭法实现的;擦拭法决定了泛型<T>
:
不能是基本类型,例如:int;
不能获取带泛型类型的Class,例如:Pair.class;
不能判断带泛型类型的类型,例如:x instanceof Pair;
不能实例化T类型,例如:new T()。