泛型是java1.5的新特性,用来表示“许多种类型”,可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
为什么要使用泛型呢?
简单说的说,一是为了提高代码的复用率,二是为了保证类型安全
1、泛型类
这是泛型使用最多的地方,很多时候创建的类都持有一种或多种不同类型的对象,很多时候我们的做法是需要什么类型的对象就创建一个什么类型的对象,也就是说一个类只能保存特定类型的对象,这样代码的复用率就很差了;也可以创建一个基类(通常是Object),通过显式的强转来创建不同类型的子类,但是这就要求开发者已经了解实际的类型。
public class Test1 { public static void main(String[] args) { // TODO Auto-generated method stub ArrayList al = new ArrayList(); Dog dog = new Dog(); Cat cat = new Cat(); al.add(cat); al.add(dog); /* * 下面的这句话必须要加强制转换,不然会报错,问题就出在这个强制转换上 * 因为从集合中获取对象的时候可能得到的不是一个Dog对象,也可能是一个Cat对象 * 如果获取的是一个Cat对象,然后把它强制的转换成一个Dog对象必然会出现错误,抛出 * ClassCastException异常,但是这种错误在编译的时候不会出现,只有在运行的时候才会出现。 * 泛型的好处就是在编译的时候就检查类型是否安全,避免了这种错误 */ Dog temp = (Dog) al.get(1); // 这就是前面说的显式强制类型转换 } } class Cat { private int age; } class Dog { private int age; }
有了泛型就不必这么做了,泛型的主要目的之一就是用来指定容器类(需要持有对象的类)要持有什么类型的对象,并且由编辑器来保证类型的正确性。把上面的代码做一下修改:
public class Test2 { public static void main(String[] args) { // TODO Auto-generated method stub ArrayList<Dog> al = new ArrayList<Dog>(); Dog dog = new Dog(); Cat cat = new Cat(); al.add(dog); /* * 此处就不需要强制转换了,因为ArrayList<Dog>已经规定了这个集合 中只能存放Dog对象 所以取出来的元素必定是Dog对象 * 当我尝试al.add(cat)的时候就会出现错误提示,因为泛型在编译的时候就会检查类型是否安全 */ Dog temp = al.get(0); } } class Cat { private int age; } class Dog { private int age; }
需要说明的是,在创建一个泛型类的对象的时候必须指明泛型参数的类型。
在上面的代码中,使用的容器类是API中提供的集合类,我们也可以在自己定义的类上面使用泛型机制。下面的这个例子模仿的是堆栈:
public class LinkedStack<T> { private class Node<U> { U item; Node<U> next; Node() { item = null; next = null; } Node(U item, Node<U> next) {//创建一个对象并连接到前一个对象 this.item = item; this.next = next; } boolean end() { return item == null && next == null; } } private Node<T> top = new Node<T> (); public void push(T item) { top = new Node<T> (item, top); } public T pop() { T result = top.item; if(!top.end()) top = top.next; return result; } public static void main(String[] args) { // TODO Auto-generated method stub LinkedStack<String> lss = new LinkedStack<String>(); for(String s : "hasers on stun!".split(" ")) lss.push(s); String s; while((s = lss.pop()) != null) System.out.println(s); } }
通过上面的几个例子,可以看到泛型机制允许我们不需要在定义容器类的时候就指明可以包含的对象的类型,当然你在创建容器类的对象的时候还是需要指明的,就是说可以向泛型容器类中存放任何类型的对象(提高了代码的复用率)。一旦你在创建一个容器类的对象的时候指明了这个容器类可以存放的对象的类型,那么这个容器类的对象就只能存放你指定的类型的对象及其子类,其他类型的对象不允许存进去,若存进去了,就没法通过编译,这也就保证了代码的安全性。
2、泛型接口
看一个比较经典的例子
新建一个泛型接口:
public interface Generator<T> { //用于创建对象 T next(); }
建一个辅助类:
public class Coffee { private static long counter = 0; private final long id = counter++; public String toString() { return getClass().getSimpleName()+" "+id; } }
建一堆继承自这个辅助类的类,就不列出来了
import java.util.Iterator; import java.util.Random; /** * 实现了Iterable接口的类是可以直接对他使用foreach的 * */ public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> { private Class[] types = { Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class }; private static Random rand = new Random(47); public CoffeeGenerator() { } private int size; public CoffeeGenerator(int sz) { size = sz; } @Override public Coffee next() { // TODO Auto-generated method stub try { return (Coffee) types[rand.nextInt(types.length)].newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } class CoffeeIterator implements Iterator<Coffee> { int count = size; @Override public boolean hasNext() { // TODO Auto-generated method stub return count > 0; } @Override public Coffee next() { // TODO Auto-generated method stub count--; return CoffeeGenerator.this.next(); } @Override public void remove() { // TODO Auto-generated method stub } } @Override public Iterator<Coffee> iterator() { // TODO Auto-generated method stub return new CoffeeIterator(); } public static void main(String[] args) { // TODO Auto-generated method stub CoffeeGenerator gen = new CoffeeGenerator(); CoffeeGenerator gen2 = new CoffeeGenerator(5); for (int i = 0; i < 5; i++) System.out.println(gen.next()); /** * foreach语句也是通过迭代器实现的,所以在使用迭代器的时候系统 会自动的调用上面的iterator()方法来生成一个迭代器对象 * */ for (Coffee c : gen2) System.out.println(c); } }
运行结果:
Americano 0
Latte 1
Americano 2
Mocha 3
Mocha 4
Breve 5
Americano 6
Latte 7
Cappuccino 8
Cappuccino 9
泛型接口能够方便的应用于工厂模式,能够创建各种不同类型的对象。
需要说明的是,基本类型不能作为泛型参数,但还是java的自动包装机制能够方便的在基本类型和对应的包装类之间进行转换。
3、泛型方法
通常在定义一个方法的时候,如果这个方法需要一个参数,那么必须在定义的时候指明参数的具体类型,这就限制了这个方法的使用范围,即这个方法只能操作这种类型的参数,泛型方法就打破了这种限制,使得同一个方法可以操作不同类型的参数。
public class GenericMethods { public <T> void f(T x) { System.out.println(x.getClass().getName()); } public static void main(String[] args) { // TODO Auto-generated method stub GenericMethods gm = new GenericMethods(); gm.f(" "); gm.f(1); gm.f(1.0); gm.f(1.0F); gm.f('c'); gm.f(gm); } }
编译器能够根据传进来的参数的类型类确定T的类型。