个人博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。java
参考书籍node
《Java核心技术:卷1》数组
先经过一个简单的例子说明下Java中泛型的用法:安全
泛型的基本形式相似于模板, 经过一个类型参数T, 你能够"私人定制"一个类,具体定制的范围包括实例变量的类型,返回值的类型和传入参数的类型函数
Foo.java测试
public class Foo <T> { // 约定实例变量的类型 private T data; // 约定返回值的类型 public T getData () { return this.data; } // 约定传入参数的类型 public void setData (T data) { this.data = data; } }
Test.javathis
public class Test { public static void main (String args[]) { Foo<String> s = new Foo<String> (); } }
泛型设计源于咱们的编写类时的一个刚需:想让咱们编写的处理类可以更加"通用", 而不是只能处理某些特定的对象或场景。或者说:咱们但愿咱们的类能实现尽量多的复用。举个栗子:通常来讲,你并不想要编写多个分别处理不一样数据类型,但内在逻辑代码却彻底同样的类。由于这些处理类可能除了数据类型变换了一下外,全部代码都彻底一致。“只要写一个模板类就OK了嘛~ 等要使用的时候再传入具体的类型,多省心”, 当你这么思考的时候:浮如今你脑海里的,就是泛型程序设计(Generic pogramming)的思想编码
在介绍Java的泛型机制以前, 先让咱们来看看, 还没加入泛型机制的“泛型程序设计”是怎样子的spa
下面咱们编写一个存储不一样的对象的列表类,列表有设置(set)和取值(get)两种操做。
假设这个列表类为ObjArray,同时尝试存储的值为String类型,则:
1.在ObjArray类里咱们维护一个数组arr, 为了未来能容纳不一样的对象, 将对象设为Object类型(全部对象的父类)
2.在实例化ObjArray后, 经过调用set方法将String存入Object类型的数组中; 而在调用get方法时, 要对取得的值作强制类型转换—从Object类型转为String类型设计
ObjArray.java:
public class ObjArray { private Object [] arr; public ObjArray(int n) { this.arr = new Object[n]; } public void set (int i, Object o) { this.arr[i] = o; } public Object get (int i) { return this.arr[i]; } }
Test.java:
/** * @description: 测试代码 */ public class Test { public static void main (String args[]) { ObjArray arr = new ObjArray(3); arr.set(0, "彭湖湾"); // get操做时要作强制类型转换 String n =(String)arr.get(0); // 输出 "彭湖湾" System.out.print(n); } }
若是不使用泛型机制,但又想要实现泛型程序设计,就会编写出相似这样的代码。
让咱们来看看使用泛型机制改进后的结果。
看起来大约是这样:
GenericArray.java
public class GenericArray<T> { public void set (int i, T o) { // ... } public T get (int i) { // ... } }
【具体代码下面给出】
Test.java:
public class Test { public static void main (String args[]) { GenericArray<String> arr = new <String>GenericArray(3); arr.set(0, "彭湖湾"); // 不用作强制类型转换啦~~ String s =arr.get(0); // 输出: 彭湖湾 System.out.print(s); } }
咱们发现,改进后的设计有如下几点好处:
1. 规范、简化了编码: 咱们不用在每次get操做时候都要作强制类型转换了
2. 良好的可读性:GenericArray<String> arr这一声明能清晰地看出GenericArray中存储的数据类型
3. 安全性:使用了泛型机制后,编译器能在set操做中检测传入的参数是否为T类型, 同时检测get操做中返回值是否为T类型,若是不经过则编译报错
了解到了泛型的这些特性后, 也许你会火烧眉毛地想要在ObjArray类里大干一场。
例如像下面这样, 用类型参数T去直接实例化一个对象, 或者是实例化一个泛型数组
惋惜的是 ......
public class GenericArray<T> { private T obj = new T (); // 编译报错 private T [] arr = new T[3]; // 编译报错 // ... }
没错, 泛型并非无所不能的, 相反, 它的做用机制受到种种条框的限制。
这里先列举泛型机制的两个限制:
1.不能实例化类型变量, 如T obj = new T ();
2. 不能实例化泛型数组,如T [] arr = new T[3];
【注意】这里不合法仅仅指实例化操做(new), 声明是容许的, 例如T [] arr
咱们如今来继续看看上面泛型设计中, GenericArray类的那部分代码:
public class GenericArray<T> { private Object [] arr; public GenericArray(int n) { this.arr = new Object[n]; } public void set (int i, T o) { this.arr[i] = o; } public T get (int i) { return (T)this.arr[i]; } }
没错, 在ObjArray类内部咱们仍然仍是用到了强制转型。看到这里也许使人有那么一点点的小失望, 毕竟仍是没有彻底跳出
初始的泛型设计的边界。 可是, 泛型的优势仍然是显而易见的, 只不过要知道的是:它并无无所不能的魔法, 并受到诸多限制。
泛型类
如前面所说,能够像下面同样定义一个泛型类
类型变量T放在类名的后面
public class Foo <T> { // 约定实例变量的类型 private T data; // 约定返回值的类型 public T getData () { return this.data; } // 约定传入参数的类型 public void setData (T data) { this.data = data; } }
泛型方法
也能够定义一个泛型方法:
泛型变量T放在修饰符(这里是public static)的后面, 返回类型的前面
public class Foo { public static <T> T getSelf (T a) { return a; } }
泛型方法能够定义在泛型类当中,也能够定义在一个普通类当中
public class Foo<T, U> { private T a; private U b; }
【注意】在Java库中,常使用E表示集合的元素类型, K和V分别表示关键字和值的类型, T(U,S)表示任意类型
ObjArray<Node> arr = new <Node>ObjArray();
可简写成:
ObjArray<Node> arr = new <>ObjArray();
当咱们实例化泛型类的时候, 咱们通常会传入一个可靠的类型值给类型变量T。 但有的时候,被定义的泛型类做为接收方,也须要对传入的类型变量T的值作一些限定和约束,例如要求它必须是某个超类的子类,或者必须实现了某个接口, 这个时候咱们就要使用extends关键字了。如:
超类SuperClass:
public class SuperClass {
}
子类SubClass:
public class SubClass extends SuperClass { }
对T使用超类类型限定:要求父类必须为SuperClass
public class Foo<T extends SuperClass> { }
测试:
public class Test { public static void main (String args[]) { Foo<SubClass> f = new Foo<SubClass>(); // 经过 Foo<String> n = new Foo<String>(); // 报错 } }
1. 对于要求实现接口, 或者继承自某个父类, 统一使用extends关键字 (没有使用implements关键字,为了追求简单)
2. 限定类型之间用 "&" 分隔
3. 若是限定类型既有超类也有接口,则:超类限定名必须放在前面,且至多只能有一个(接口能够有多个)
这个书写规范和类的继承和接口的实现所遵循的规则是一致的(<1>不容许类多继承,但容许接口多继承<2>书写类的时候类的继承是写在接口实现前面的)
// 传入的T必须是SuperClass的子类,且实现了Comparable接口 public class Foo<T extends SuperClass&Comparable> { }
【注意】: 上面的SuperClass和Comparable不能颠倒顺序
泛型类型的引入引起了一些关于泛型对象继承关系的有趣(?)问题。
在Java中, 若是两个类是父类和子类的关系,那么子类的实例也都是父类的实例,这意味着:
一个子类的实例能够赋给一个超类的变量:
SubClass sub = new SubClass(); SuperClass sup = sub;
当引入了泛型之后, 有趣(?)的问题来了:
咱们经过两对父子类List/ArrayList, Employee/Manager来讲明这个问题
(咱们已经知道List是ArrayList的父类(抽象类),这里假设Employee是Manager的父类)
1. ArrayList<Employee> 和 ArrayList<Manager>之间有继承关系吗?(ArrayList<Manager>的实例可否赋给ArrayList<Employee>变量?)
2. List<Employee> 和 ArrayList<Employee>之间有继承关系吗?(ArrayList<Employee>的实例可否赋给 List<Employee>变量?)
3. ArrayList和ArrayList<Employee>之间有继承关系吗?(ArrayList<Employee>的实例可否赋给ArrayList变量?)
答案以下:
对1: 没有继承关系; 不能
ArrayList<Manager> ae = new ArrayList<Manager>(); ArrayList<Employee> am = ae; // 报错
对2: 有继承关系; 能够
ArrayList<Employee> al = new ArrayList<>(); List<Employee> l = al; // 经过
对3: 有继承关系; 能够
ArrayList<Employee> ae = new ArrayList<>(); ArrayList a = ae;
下面用三幅图描述上述关系:
描述下1,2的关系
对上面三点作个总结:
1. 类名相同,但类型变量T不一样的两个泛型类没有什么联系,固然也没有继承关系(ArrayList<Manager>和ArrayList<Employee>)
2. 类型变量T相同,同时原本就是父子关系的两个类, 做为泛型类依然保持继承关系 (ArrayList<Employee>和List<Employee>)
3. 某个类的原始类型,和其对应的泛型类能够看做有“继承关系”(ArrayList和ArrayList<Employee>)
引用一幅不太清晰的图
问题出如今上面所述规范中的第二点:ArrayList<Manager> 和 ArrayList<Employee>之间没有继承关系。
这意味着,若是你像下面同样编写一个处理ArrayList<Employee>的方法
public class Foo { public static void handleArr (ArrayList<Employee> ae) { // ... } }
你将没法用它来处理ArrayList<Manager>:
public static void main (String args[]) { ArrayList<Manager> am = new ArrayList<Manager>(); Foo.handleArr(am); // 报错,类型不匹配 }
如今咱们想要:“handleArr方法不只仅能处理ArrayList<Employee>, 并且还能处理ArrayList<X> (这里X表明Employee和它子类的集合)”。因而这时候通配符?就出现了:ArrayList<? extends Employee>可以匹配ArrayList<Manager>, 由于ArrayList<? extends Employee>是 ArrayList的父类
如今咱们的例子变成了:
public class Foo { public static void handleArr (ArrayList<? extends Employee> ae) { // ... } } public static void main (String args[]) { ArrayList<Manager> am = new ArrayList<Manager>(); Foo.handleArr(am); // 能够运行啦! }
?统配不只能够用于匹配子类型, 还能用于匹配父类型:
<? super Manager>
上面咱们介绍了泛型的一些约束,例如不能直接实例化实例化类型变量和泛型数组,这里和其余约束一块儿作个总结:
1. 不能实例化类型变量
T obj = new T (); // 报错, 提示: Type parameter 'T' cannot be instantiated directly
解决方案:
若是实在要建立一个泛型对象的话, 可使用反射:
public class GenericObj<T> { private T obj; public GenericObj(Class<T> c){ try { obj = c.newInstance(); // 利用反射建立实例 } catch (Exception e) { e.printStackTrace(); } } }
/** * @description: 测试代码 */ public class Test { public static void main (String args[]) { // 经过 GenericObj<String> go = new GenericObj<> (String.class); } }
由于Class类自己就是泛型, 而String.class是Class<T>的实例,
2. 不能实例化泛型数组,如T [] arr = new T[3];
private T [] arr = new T[3]; // 报错, 提示: Type parameter 'T' cannot be instantiated directly
解决方法一:
上文所提到的,建立Object类型的数组,而后获取时转型为T类型:
public class GenericArray<T> { private Object [] arr; public GenericArray(int n) { this.arr = new Object[n]; } public void set (int i, T o) { this.arr[i] = o; } public T get (int i) { return (T)this.arr[i]; } }
解决方法二: 利用反射
这里使用反射机制中的Array.newInstance方法建立泛型数组
GenericArray.java
public class GenericArray<T> { private T [] arr; public GenericArray(Class<T> type, int n) { arr = (T[])Array.newInstance(type, n); // 利用反射建立泛型类型的数组 } public void set (int i, T o) { this.arr[i] = o; } public T get (int i) { return (T)this.arr[i]; } }
Test.java
/** * @description: 测试代码 */ public class Test { public static void main (String args[]) { GenericArray<String> genericArr = new GenericArray<>(String.class, 5); genericArr.set(0, "penghuwan"); System.out.println(genericArr.get(0)); // 输出 "penghuwan" } }
3. 不能在泛型类的静态上下文中使用类型变量
public class Foo<T> { private static T t; public static T get () { // 报错, 提示: 'Foo.this' can not be referenced from a static context return T; } }
注意这里说的是泛型类的状况下。若是是在一个静态泛型方法中是可使用类型变量的
public class Foo { public static<T> T get (T t) { // 经过 return t; } }
(这里的泛型方法处在一个非泛型类中)
4. 不能抛出或者捕获泛型类的实例
不能抛出或者捕获泛型类的实例:
// 报错 提示:Generic class may not extend java.lang.throwable public class Problem<T> extends Exception { }
甚至扩展Throwable也是不合法的
public class Foo { public static <T extends Throwable> void doWork () { try { // 报错 提示: Cannot catch type parameters }catch (T t) { } } }
但在异常规范中使用泛型变量是容许的
// 能经过 public class Foo { public static <T extends Throwable> void doWork (T t) throws T { try { // ... }catch (Throwable realCause) { throw t; } } }
1. 不能使用基本类型的值做为类型变量的值
Foo<int> node = new Foo<int> (); // 非法
应该选用这些基本类型对应的包装类型
Foo<Integer> node = new Foo<Integer> ();
2. 不能建立泛型类的数组
public static void main (String args[]) { Foo<Node> [] f =new Foo<Node> [6]; // 报错 }
解决方法:
能够声明通配类型的数组, 而后作强制转换
Foo<Node> [] f =(Foo<Node> [])new Foo<?> [6]; // 经过