浏览以下内容前,请点击并阅读 声明
泛型的使用能使类型名称作为类或者接口定义中的参数,就像一般的参数一样,使得定义的类型通用性更强。
泛型的优势:
- 编译具有严格的类型检查
java编译器对于泛型代码的类型检查更加严格,能够发现普通代码中的一些运行时错误。
- 消除类型转化
//如下代码未使用泛型,需要进行类型的转化 List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0); //泛型的使用可以不适用类型转化 List<String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); // no cast
- 能够使程序员实现通用的算法
通过使用泛型,使得一类不同的类型能够进行通用的运算。
1 泛型
泛型是将类型参数化的类或者接口。
1.1 泛型的声明
一般泛型的声明类似如下:
class name<T1, T2, ..., Tn> { /* ... */ }
尖括号中的参数就是类型参数,参数由逗号隔开,类型参数可以是任何非基本数据类型的任何类型,泛型的接口声明与上述泛型类类似。
1.2 泛型参数命名
一般情况下,泛型类型参数的名称是单个大写字母,和变量名称鲜明地区分开来。
最通用的参数类型参数名称为:
- E - 元素 (被java集合框架应用)
- K - 键
- N - 数字
- T - 类型
- V - 值
- S,U,V 等等 - 第二,第三, 第四类型
在javaSE API中这些名称被广泛使用
1.3 调用和实例化泛型
如需引用一个泛型,首先要进行一个泛型的调用,如下所示:
//传入类型参数,如下为String作为类型参数 ArrayList<String> list;
上述代码可以看做个调用方法类似,不过是以类型为参数,这一过程叫参数化类型,实例化泛型的语法如下:
//一下声明和实例化一步完成 ArrayList<String> list=new ArrayList<String>();
1.4 钻石
java7以后,只要编译器能够根据代码的上下文判断类型参数,就可以将泛型的构造器的类型实参留空(<>)由于空的尖括号形状就像钻石,所以非正式的成为钻石,如上述代码可以简写为:
//注意构造器内的参数已经省略 ArrayList<String> list=new ArrayList<>();
1.5 参数化类型作为类型参数
泛型的类型参数也可以是参数话的泛型,如:
//省略构造器类型参数 ArrayList<List<String>> list=new ArrayList<>();
2 原始类型
2.1定义
原始类型是指没有类型参数的泛型。例如一下声明一个原始类型:
//ArrayList是一个泛型,因此List变量是原始类型 ArrayList list=new ArrayList();
原始类型的旧的java版本的遗产,因为许多API类如集合类在JDK5之前不是泛型,为了向下兼容,将一个参数化的泛型对象赋值给一个原始类型是允许的:
Box<String> stringBox = new Box<>(); Box rawBox = stringBox; // OK
不过反过来,将一个原始类型赋值给一个参数化的泛型,编译器将会给出警告:
Box rawBox = new Box(); // rawBox是 Box<T>的原始类型 Box<Integer> intBox = rawBox; // 警告: unchecked conversion
如果使用原始类型去调用队形的泛型的泛型方法,同样也会得到警告:
Box<String> stringBox = new Box<>(); Box rawBox = stringBox; rawBox.set(8); // warning: unchecked invocation to set(T)
编译器警告表明原始类型绕过了类型的检查,而将可能出错的风险留到了运行时,所以尽量不要使用原始类型。
2.2 未检查错误信息
如之前所提到的,当泛型和传统语法混用时,你将会遇到一些如下的警告消息:
Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
“unchecked”(未检查的)这一术语表明编译器没有足够的关于类型信息来执行检查以确保类型的安全,编译器默认警用未检查警告,但是会给出提示,如果想要启用未检查警告,在编译时加入参数 -Xlint:unchecked。
如果想要完全禁用未检查警告,可以编译时加入参数-Xlint:-unchecked(注意与上述参数区别)或者使用注释@SuppressWarnings("unchecked")。
3 泛型方法
泛型方法是指引入自身的参数类型的方法,就像泛型的类型参数一样,不过方法的类型参数的使用范围仅限于方法自身。可以定义静态和非静态的泛型方法,同时也可以定义使用泛型构造器。
泛型方法的语法含有位于方括号内的类型参数,位于方法返回类型之前,当然,非泛型的类也可以包含泛型方法。
如下举例说明泛型方法的声明:
public class Util { //以下方法为泛型方法 public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); } } public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } public K getKey() { return key; } public V getValue() { return value; } }
完整的调用泛型方法的语法为:
//以下语句利用了上述定义的类 Pair<Integer, String> p1 = new Pair<>(1, "apple"); Pair<Integer, String> p2 = new Pair<>(2, "pear"); boolean same = Util.<Integer, String>compare(p1, p2);
当然,如果编译其能够推测出类型,可省略泛型方法的类型参数:
Pair<Integer, String> p1 = new Pair<>(1, "apple"); Pair<Integer, String> p2 = new Pair<>(2, "pear"); boolean same = Util.compare(p1, p2);
4 限制类型参数范围
有时候你可能需要限定泛型的类型参数,比如限定某个泛型的类型参数只能为Number或者是其子类或者继承类。
声明类型参数的限定,就是在参数名称之后跟上extends关键词,然后跟上其上限,如Number,在这里extends通常意义上是指类的extends和接口的implements。,如一下方法的声明:
public <U extends Number> void inspect(U u){ System.out.println("T: " + t.getClass().getName()); System.out.println("U: " + u.getClass().getName()); }
一个类型参数可以有多重限定,即extends关键词后跟多个上限,用符号&隔开:
class D <T extends A & B & C> { /* ... */ }
5 泛型,继承和子类型
java中可以将一个类型的对象赋值给另外一个类型的变量,如果两个类型相兼容的话,如可以将Integer类型的对象赋值给类型为Object的变量。在面向对象的术语中,这是一种叫做“是一个”的关系,如Integer类型是一个Object类型,因此允许上述的赋值。方法包括泛型的方法的参数的传递也是如此,如
//如下进行类型参数化的ArrayList类型的元素类型为Number ArrayList<Number> list=new ArrayLIst<Number>(); //其add方法参数类型也是Number,也可以使用其子类Integer的实例 list.add(new Integer(10));
但是对于泛型的子类型关系,与普通的类型是有区别的,如下图所示:
箭头表示子类型的关系,如Integer是Number的子类型,而Box<Integer>则不是Box<Number>的子类型,Box<Integer>和Box<Number>的共同的父类型是Object。
你可以通过继承或者实现一个泛型来成为该泛型一个子类型。类或者接口的类型参数之间的关系是由extends和implements语句决定的。
比如集合类,ArrayList<E>实现List<E>,List<E>继承Collection<E>所以ArrayList<String>是List<String>的子类型,List<String>是Collection<String>的子类型,只要不变更类型参数,这种继承关系就会保留:
现在自定义一个新的继承List接口的接口:
//注意类型参数的名称 interface PayloadList<E,P> extends List<E> { void setPayload(int index, P val); ... }
类型参数化的类型则由如下关系: