从JDK1.5开始提供泛型的概念,泛型实质上就是使程序员定义安全的类型。在没有出现泛型之前,java也提供了对Object的引用"任意化"操作,这种任意化操作就是对Object引用进行"向下转型"及"向上转型"操作,但某些强制类型转换的错误也许不会被编译器捕捉,而在运行后出现异常,可见强制类型转换存在安全隐患。
1. 定义泛型
在JDK1.5以后出现了泛型机制,其语法如下:
类名<T>
其中T代表了一个类型的名称。
实例如下所示:
public class OverClass<T> { private T over; public T getOver() { return over; } public void setOver(T over) { this.over = over; } public static void main(String[] args) { // 实例化一个Boolean型的对象 OverClass<Boolean> over1 = new OverClass<Boolean>(); // 实例化一个Float型的对象 OverClass<Float> over2 = new OverClass<Float>(); over1.setOver(true);// 不需要进行类型转换 over2.setOver(12.3f);// 不需要进行类型转换 Boolean b = over1.getOver(); Float f = over2.getOver(); System.out.println(b); System.out.println(f); } }
从上面的程序可以看出,使用泛型定义的类在声明该类对象时可以根据不同的需求指定<T>真正的类型,而在使用类中的方法传递或返回数据类型时将不再需要进行类型转换操作,而是使用在声明泛型类对象时<>符号中设置的数据类型。
使用泛型这种形式将不会发生ClassCastException异常,因为在编译器中就可以检查类型匹配是否正确。
在定义泛型类时,一般将类型名称使用T来表述,而容器的元素使用E来表述。
2. 泛型的常规用法
2.1 定义泛型类时声明多个类型
在定义泛型类时可以声明多个类型,语法如下:
MutiOverClass<T1,T2>
其中,MutiOverClass代表泛型类名称,T1和T2为可能被定义的类型。
这样在实例化指定类型的对象时就可以指定多个类型,例如:
MutiOverClass<Boolean,Float> m=new MutiOverClass<Boolean,Float>;
2.2 定义泛型类时声明数组类型
定义泛型类时也可以声明数组类型。如下所示实例:
public class ArrayClass<T> { private T[] array; public T[] getT() { return array; } public void setT(T[] array) { this.array = array; } public static void main(String[] args) { ArrayClass<String> a = new ArrayClass<String>(); String[] array = { "成员1", "成员2", "成员3", "成员4", "成员5" }; a.setT(array);// 调用Set()方法 for (int i = 0; i < a.getT().length; i++) { System.out.println(a.getT()[i]);// 调用getT()方法返回数组中的值 } } }
运行效果如下:
可以使用泛型声明一个数组,但是不可以使用泛型来建立数组的实例。如下所示是错误的用法:
public class ArrayClass<T> { //private T[] array=new T[10];//不可以使用泛型来建立数组的实例 }
3. 集合类声明容器的元素
可以使用K和V两个字符代表容器中的键值和与键值对相对应的具体值。如下示例所示:
public class MutiOverClass<K, V> { public Map<K, V> m = new HashMap<K, V>();// 定义一个集合HashMap实例 // 设置put()方法,将对应的键值和键名存入集合对象中 public V get(K k) { return m.get(k); } public void put(K k, V v) { m.put(k, v); } public static void main(String[] args) { // 实例化泛型类对象 MutiOverClass<Integer, String> mu = new MutiOverClass<Integer, String>(); for (int i = 0; i < 5; i++) { // 根据集合的长度循环将键名与具体值放入集合中 mu.put(i, "我是集合成员:" + i); } for (int i = 0; i < mu.m.size(); i++) { // 调用get方法获取集合中的值 System.out.println(mu.get(i)); } } }
其实上面定义集合的泛型类在集合框架中已经都被泛型化了,可以直接使用。
4. 泛型的高级用法
泛型的高级用法包括限制泛型可用类型和使用类型通配符等。
4.1 限制泛型可用类型
默认可以使用任何类型来实例化一个泛型类对象,但java中也对泛型类实例的类型作了限制,语法如下:
Class 类名称<T extends anyClass>
其中,anyClass是指某个接口或类。
使用泛型限制后,泛型类的类型必须是实现或继承了anyClass这个接口或类。无论anyClass是接口或类,在进行泛型限制时,都必须使用extends关键字。示例如下所示:
public class LimitClass<T extends List> { public static void main(String[] args) { // 可以实例化已经实现List接口的类 LimitClass<ArrayList> l1 = new LimitClass<ArrayList>(); LimitClass<LinkedList> l2 = new LimitClass<LinkedList>(); // 这句是错误的,因为HashMap没有实现List()接口 // LimitClass<HashMap> l3 = new LimitClass<HashMap>(); } }
4.2 使用类型通配符
在泛型机制中提供了类型通配符,其主要作用是在创建一个泛型类对象时限制这个泛型类的类型实现或继承某个接口或类的子类。要声明这样一个对象可以使用"?"通配符来表示,同时依然使用extends关键字来对泛型加以限制。
使用泛型类型通配符的语法如下:
泛型类名称<? extends List> a=null;
其中,<? extends List>表示类型未知,当需要使用该泛型对象时,可以单独实例化。
如下所示:
A<? extends List> a=null; a=new A<ArrayList>(); a=new A<LinkedList>();
如果实例化没有实现List接口的泛型对象,编译器将会出错。
除了可以实例化一个限制泛型类型的实例之外,还可以将该实例放置在方法的参数中。如下所示:
public void doSomething(A<? extends List>){}
在上述代码中,定义方式有效限制了传人doSomething()方法的参数类型。
如果使用A<?>这种形式实例化泛型类对象,则默认表示可以将A指定为可以实例化Object及以下的子类类型。如下所示:
List<String> l1=new ArrayList<String>(); l1.add("成员"); List<?> l2=l1; List<?> l3=new LinkedList<Integer>();
泛型类型限制除了可以向下限制之外,还可以进行向上限制,只要在定义时使用super关键字即可。例如,"A<? super List> a=null;" 这样定义后,对象a只接受List接口或上层父类类型,如"
a=new A<Object>();"
4.3 继承泛型类与实现泛型接口
定义为泛型的类和接口也可以被继承与实现。如下所示:
public class ExtendClass<T1> { } class SubClass<T1, T2, T3> extends ExtendClass<T1> { }
如果在SubClass类继承ExtendClass类时保留父类的泛型类型,需要在继承时指明,如果没有指明,直接使用extends ExtendClass<T1>语句进行继承操作,则SubClass类中的T1,T2和T3都会自动变为Object,所以在一般情况下都会将父类的泛型类型保留。
定义的泛型接口也可以被实现,如下所示:
interface i<T1> { } class SubClass2<T1, T2, T3> implements i<T1> { }
5. 泛型用法总结
泛型的类型参数只能是类类型,不可以是简单类型,如A<int> 这种泛型定义就是错误的。
泛型的类型个数可以是多个。
可以使用extends关键字限制泛型的类型。
可以使用通配符限制泛型的类型。