泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
在使用泛型的版本中并没有减少键盘录入;实际上,比使用强制类型转换的版本需要做更多键入。使用泛型只是带来了附加的类型安全。因为编译器知道关于您将放进Map中的键和值的类型的更多信息,所以类型检查从执行时挪到了编译时,这会提高可靠性并加快开发速度。
对于没有使用泛型处理的代码,在JDK5中,系统会出现编译警告,提示代码为”raw type”。 这是兼容的,如果不想看到这个信息,可以做如下处理:
1, 使用 –source 1.4 参数编译。
2, 使用@SupressWarnings(“unchecked”) annotaion
3, 升级为使用泛型
如果你声明一个泛型List<atype>,那么List的实现中,atype可以是如下类型:
1, atype的实例
2, atype子类的实例(如果atype是一个class)
3, atype的实现类的实例(如果atype是一个接口)
泛型类在多个方法签名间实施类型约束。在List<V>中,类型参数V出现在get()、add()、contains()等方法的签名中。当创建一个Map<K, V>类型的变量时,您就在方法之间声明了一个类型约束。您传递给add()的值将与get()返回的值的类型相同。 类似地,之所以声明泛型方法,一般是因为您想要在该方法的多个参数之间声明一个类型约束。例如,下面代码中的ifThenElse()方法,根据它的第一个参数的布尔值,它将返回第二个或第三个参数,这就做到了参数类型与返回参数类型的一致性:
public <T> T ifThenElse(boolean b, T first, T second) {
return b ? first : second;
}
为什么您选择使用泛型方法,而不是将类型T添加到类定义呢?(至少)有两种情况应该这样做:
* 当泛型方法是静态的时,这种情况下不能使用类类型参数(泛型类)。
* 当 T 上的类型约束对于方法真正是局部的时,这意味着没有另一个方法签名中使用相同 类型 T 的约束。泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名。
对于常见的泛型模式,推荐的名称是:
* K —— 键,比如映射的键。
* V —— 值,比如 List 和 Set 的内容,或者 Map 中的值。
* E —— 异常类。
* T —— 泛型。
比如现在有三个类,Shape类以及两个子类:Circle、Rectangle。
Circle c = new Circle();
List<Shape> s1 = new ArrayList<Shape>();//正确
s1.add(c);//正确
List<Shape> s2 = new ArrayList<Circle>();//编译错误
List<? extends Shape> s3 = new ArrayList<Shape>();//正确
List<? extends Shape> s4 = new ArrayList<Circle>();//正确
s3.add(c);//编译错误
s4.add(c);//编译错误
泛型和Java对象一样,都有自己的类型,下面两个就是完全独立,没有关系的:
List<Object> list1 = new ArrayList<Object>();
List<String> list2 = new ArrayList<String>();
虽然String是Object的子类,但是List<String>可不是List<Object>的子类。
List<String>不是List<Object>的子类,不是String与Object的关系,就是说List<String>不隶属于list<Object>,他们不是继承关系,所以是不行的,这里的extends是表示限制的。所以下面代码第2行是错误的:
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2
List<?>是任何泛型List的父类型,所以您完全可以将List<Object>、List<Integer>或List<List<List<Flutzpah>>>赋值给它。但这样声明就不对了: List<?> myList = new ArrayList<?>();
List<?>中的元素类型是未知的、不确定的,我们不能向其中添加任何确定的类型对象。因为不知道那是什么类型,所以我们无法传任何东西进去。但唯一的例外是null,它能代表所有类型(即是这种类型也是那种类型),但从List<?>泛型集合中取出的元素类型为Object。
Collection<?> c = new ArrayList<String>();
c.add(null);//可以放进去
c.add(new Object()); // 编译器不能对Collection<?>的类型参数作出足够严密的推理,以确定将Object传递给c.add()是类型安全的。所以编译器将不允许您这么做
for (Object o : c){//取出的元素类型为Object
System.out.println(o);
}
JDK5推出upper bounded ,格式如下:
GenericType <? extends uppderBoundedType>
反之,JDK5还有一个lower bounded,List<? super Integer>可以接收Integer或者它的父类。 <? super T> 语法将泛型类限制为所有T的超类(包括T自身),但只能用于方法的参数中,不可以在返回值用与泛型类中。如果不加以限定,假设某个函数头为<? super Manager> get(),由于编译器不知道该方法究竟会返回什么类,这样就只能用Object类来接收了,这样的话不就又回到泛型以前了吗? 所以这是不允许的。
class C<T extends Comparable<? super T> & Serializable>
我们来分析以下这句,T extends Comparable这个是对上限的限制(意思是不超过它,但可在等于它),Comparable< super T>这个是下限的限制,Serializable是第2个上限。一个指定的类型参数可以具有一个或多个上限。具有多重限制的类型参数可以用于访问它的每个限制的方法和域。
不能实例化泛型类型的数组(new List<String>[3] 是不合法的),除非类型参数是一个未绑定的通配符(new List<?>[3] 是合法的)
JDK 1.5 还允许将「不被method实际用到」的类型参数以符号 '?' 表示,例如:
public static List<?> gMethod (List<?> list){
return list; // 本例简单原封不动的返回
}
此例gMethod()接受一个List(无论元素的类型是什么),返回一个List(无论其元素类型是什么)。由于不存在(或说不在乎)参数类型(因为method 內根本不去用它),也就不必如平常一样在返回类型之前写出<T>来告知编译器了。上面这个例子无法真正表现出符号'?'的用途,真正的好例子請看JDK1.5 的java.util.Collections 的源码
1.4:
public static Object max(Collection coll) {
Iterator i = coll.iterator();
Comparable candidate = (Comparable)(i.next());
while(i.hasNext()) {
Comparable next = (Comparable)(i.next());
if (next.compareTo(candidate) > 0)
candidate = next;
}
return candidate;
}
1.5:
public static <T extends③ Object & Comparable④<? super⑤ T>> T⑥ max(Collection①<? extends② T> coll) {
Iterator<? extends T> i = coll.iterator();
T candidate = i.next();
while(i.hasNext()) {
T next = i.next();
if (next.compareTo(candidate) > 0)
candidate = next;
}
return candidate;
}
1. max()接收一个Collection object。
2. 该Collection object 所含元素必須是T-derived object。
3. T 必須继承Object(这倒不必明说,因为Java 必定如此)。
4. T 必須实现Comparable接口。
5. Comparable 所比较的对象类型必須是T 的super type。
6. max()返回T object。
针对上面如此怪异的语法,给个实际的例子就清楚多了:
LinkedList<Shape> sList = new LinkedList<Shape>();
...
Shape s = Collections.max(sList);
我们让Collections.max()接受一个LinkedList<Shape>,这是个Collections object(符合条件1),其中每个元素都是Shape-derived objects(符合条件2),因此本例中的T 就是Shape。Shape 的确实继承自Object(符合条件3),並且必須实现Comparable(才能符合條件4),而被比较对象的类型必須是Shape 的super class(才能符合条件5)。max()比較所得之最大值以Shape 表示(符合条件6)——這是合理的,因为不知道比较出來的結果会是Rect 或Circle,但无论如何它们都可以向上转型为Shape。
为了完成上述的条件4 和条件5,先前的Shape 必须修改,使得可比较。也就是说Shape 必须实现Comparable 接口,其中针对compareTo()用上了典型的Template Method 设计模式,再令每一个Shape-derived classes都实现L(),這就大功告成了。
public abstract class Shape implements Comparable<Shape> {
...
public abstract double L(); //计算周长
public int compareTo(Shape o) { //假设「以周长为比较依据」合理!
return (this.L() < o.L() ? -1 : (this.L() == o.L() ? 0 : 1));
}
}
泛型类型的使用限制:
* 不应在静态上下文环境中引用类型参数
* 不能用基本类型实例化泛型类型参数
* 不能在数据类型转换或 instanceof 操作中使用类型参数 new Object() instanceof T
* 不能在 new 操作中使用类型参数 new T()
* 不能在类定义的 implements 或 extends 子句中使用类型参数 class Test<T> implements T
/*
* 编译通不过,方法重复声明,因为ArrayList<String> 与
* ArrayList<Integer> 为同一类型
*/
public class X{
void set(ArrayList<String> y) {
}
void set(ArrayList<Integer> y){
}
}
ArrayList<String>,ArrayList<Integer>不同于 c++ 中的 template生成多个类 ,java 中仍是一个类 ,只不过生成字节码文件时替换参数类型为指定上限(默认为Object),编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除,另外Java 语言中的泛型不能接受基本类型作为类型参数――它只能接受引用类型。
class Template{
public static void main(String[] args){
C<Number> x = new C<Number>();
C<Integer> y = new C<Integer>();
x.get();
//error,因为方法的参数类型已由调用它的实例确/定为Number,
//所以类型参数不能是Integer,但可以使用通配符参数来解决:get(C<? extends T> t)或这样也行<X extends T> void get(C<X> t)
x.get(y);
}
}
class C<T extends Number>{
private T num;
public void get(C<T> t){
}
public void get(){
//可以直接调用类型参数对象的方法
System.out.println(num.intValue());
}
}
仅从某个结构中获取值时使用extends 通配符;仅将值放入某个结构时使用 super 通配符;同时执行以上两种操作时不要使用通配符。看看Collections的copy方法:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
...
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
...
}
之所以这样,是因为这样确保源集合中的元素可以放入目标集合中,如果目标集合的泛型比源还窄的话,会出问题,这样使用super限制了目标集合一定比源集合类型要宽,编译时就起到了类型安全检测的作用了。
* 类型安全
。 泛型的一个主要目标就是提高 Java 程序的类型安全。使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果没有泛型,那么类型的安全性主要由程序员来把握,这显然不如带有泛型的程序安全性高。
* 消除强制类型转换
。泛型可以消除源代码中的许多强制类型转换,这样可以使代码更加可读,并减少出错的机会。
* 向后兼容。支持泛型的 Java 编译器(例如 JDK5.0 中的 Javac)可以用来编译经过泛型扩充的 Java 程序(GJ 程序),但是现有的没有使用泛型扩充的 Java 程序仍然可以用这些编译器来编译。
* 层次清晰,恪守规范
。无论被编译的源程序是否使用泛型扩充,编译生成的字节码均可被虚拟机接受并执行。也就是说不管编译器的输入是 GJ 程序,还是一般的 Java 程序,经过编译后的字节码都严格遵循《Java 虚拟机规范》中对字节码的要求。可见,泛型主要是在编译器层面实现的,它对于 Java 虚拟机是透明的。
* 性能收益
。目前来讲,用 GJ 编写的代码和一般的 Java 代码在效率上是非常接近的。 但是由于泛型会给 Java 编译器和虚拟机带来更多的类型信息,因此利用这些信息对 Java 程序做进一步优化将成为可能。
总之:为了避免JVM大的变动,java选择了在字节码级别保持向后兼容,而编译器则仅仅负责了检查以及自动添加强制转换代码来生成能兼容以前jvm的设计。