1. 泛型 (Generics)介绍 详情参看 Oracle 官方文档
泛型 使更多的 bug 在 编译时期就可以被发现 , 为代码增加稳定性 。
泛型带来的好处 :
★ 在编译时期 强类型检查
★ 转换的减少
★ 使程序实现了 泛型算法 ,可以自定义,更容易使用,类型安全的,并且更容易读
1.1. 泛型 类型 (Gerneric Types)
泛型类型 是 一个通过类型参数化的 泛型 类或者接口 。
泛型 类 的定义形式如下 : 类型参数 被 <> 包围 ,紧跟在 类名的后面 ,声明了形参(type parameters 也被成为类型变量 type variables)为 T1,T2...Tn,类型变量可以是指定的任意非原始类型 ;同样适用于接口
class name<T1,T2,...,Tn> { /* ... */ }
非泛型和 泛型 栗子 :
非泛型 Box:
/** * Created by xlch on 2016/7/21. */ public class Box { private Object object; public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } @Test public void test(){ Box Box = new Box(); Box.setObject(2); //可以传任何类型的值 Box.setObject("333"); } }
泛型类 Box :
/** * Created by xlch on 2016/7/21. */ public class Box<T> { //泛型类型声明 // T 代表 类型参数 private T t; public void set(T t){ this.t = t; } public T get(){ return t; } @Test public void test(){ Box<Integer> Box = new Box<>(); // JAVA SE 7 的钻石语法:可以省略后面 <> 中的类型参数 Box.set(222); // 只能传递 Integer 类型的值,否则编译期就会报错 } }
按照惯例 , 类型参数 的命名规则是 : 一个大写字母 ,最常用的 类型参数名称如下 :
★ E - Element
★ K - Key
★ T - Type
★ N - Number
★ V -Value
★ S ,U,V - 2nd,3trd,4th
调用和实例化泛型类型
调用泛型类型时 ,必须执行泛型类型调用 ,即使用实际的类或者接口代替 类型参数(如 T) :参数化类型(parameterized type)
Box<Integer> integerBox; //和普通的变量声明一样,声明了一个持有 "Integer"引用的 integerBox 变量实例化泛型类 :
Box<Integer> integerBox = new Box<Integer>(); Box<Integer> integerBox = new Box<>(); //java SE 7 的 钻石语法 (Diamond)
·type Parameter 和 Type Argument 术语 :这两个术语的意思是不相同的 。 提供 type arguments 的目的是为了创建参数化的类型。因此,Foo<T> 的 T 属于 type parameter,Foo<String> 的 String 属于 type argument .有点形参实参的感觉。
可以定义单个类型参数 ,也可以定义多个类型参数 (如JAVA SE 中集合的定义)
1.2. 原始类型 (Raw Type) :在调用或者实例化泛型类型时没有类型参数 的类或者接口
栗子 : 应该避免使用 原始类型
/** * Created by xlch on 2016/7/21. */ public class Foo<T> { public void set(T t){ System.out.println(t); } @Test public void test(){ Foo<Integer> foo = new Foo<>(); foo.set(4); Foo foo1 = new Foo(); // 这个 Foo 便是 Foo<Integer> 的 原始类型 ,raw type foo1.set("333"); // ok foo1.set(33); // ok Foo foo2 = foo; // 会有警告 foo2.set("22"); // Unchecked call set...的警告 Foo<Integer> foo3 = foo1; //会有警告 } }
1.3 . 泛型方法 (Generic Method)
利用类型参数 定义的方法 ,类型参数只能该方法可以使用 ,静态和非静态 泛型以及 泛型构造函数都是可以的 。
语法要求:
★ 括号内使用类型参数
★ 方法返回类型之前使用类型参数 ,而对于静态的泛型方法 , 类型参数必须出现在方法的返回类型之前 ,static 关键字之后 ;此处的 类型参数可以使用类型的限制 ,extends or super
栗子 :
泛型类 :
/** * Created by xlch on 2016/7/21. */ public class Pair<K,V> { private K k; //类型参数 type parameter private V v; //类型参数 public Pair(K k,V v) { this.k = k; this.v = v; } public K getK() { return k; } public void setK(K k) { this.k = k; } public V getV() { return v; } public void setV(V v) { this.v = v; } }
静态方法 util 类
/** * Created by xlch on 2016/7/21. */ public class Util { // 静态泛型方法 public static <K,V> boolean compare(Pair<K,V> p1,Pair <K,V> p2){ return p1.getK().equals(p2.getK()) && p1.getV().equals(p2.getV()); } @Test public void test(){ Pair<Integer,String> p1 = new Pair<>(1,"apple"); Pair<Integer,String> p2 = new Pair<>(1,"apple"); boolean same = Util.<Integer,String>compare(p1,p2); boolean equal = Util.compare(p1,p2); //可省略 ,类型推断 type inference System.out.println(same); System.out.println(equal); } }
1.4. 受限的类型参数 :即限制类型参数
即限制 方法参数的传入类型 ,这里使用 extends 关键字,但是这里的 extends 和 类或者 接口的继承是不同的 。
/** * Created by xlch on 2016/7/22. */ public class Box<T> { private T t; public T getT() { return t; } public void setT(T t) { this.t = t; } public <U extends Number>void inspect(U u){ // extends 类型参数限制 ,只能传入 Number的实例 或者 Number 的子类实例 System.out.println(u); } @Test public void test(){ Box<Integer> Box = new Box<>(); Box.inspect("hi");//这时候会报错,String 类型不能作为传入的参数类型 } }
多个限制的使用语法 :
使用 & 连接 ,但是如果限制中有一个是类,则必须第一个被指定,其他接口跟在后面,否则报错 。 形如 :
<T extends B1 & B2 & B3>
栗子 :
Class A { /* ... */ } interface B { /* ... */ } interface C { /* ... */ } class D <T extends A & B & C> { /* ... */ } // 类必须放在第一个 ,后面放接口,否则报错 class D <T extends B & A & C> { /* ... */ } // compile-time error
1.5 泛型 , 继承 ,子类
Object someObject = new Object(); Integer someInteger = new Integer(10); someObject = someInteger; // OK public void someMethod(Number n) { /* ... */ } someMethod(new Integer(10)); // OK someMethod(new Double(10.1)); // OK Box<Number> Box = new Box<Number>(); Box.add(new Integer(10)); // OK Box.add(new Double(10.1)); // OK public void BoxTest(Box<Number> n) { /* ... */ } BoxTest(Box<Number> nBox); //ok BoxTest(Box<Integer> iBox); // wrong
关系图如图 :
泛型类和子类 :
1.6 类型推断 (Type inference)
BoxDemo.<Integer>addBox(Integer.valueOf(10),listOfIntegerBoxes); //完全写法 BoxDemo.addBox(Integer.valueOf(20),listOfIntegerBoxes); //可省略 ,java 编译时期自动类型推断 类型为 Integer
类型推断和 实例化泛型类 : java SE 7 推出的钻石语法
Map<String,List<String>> myMap = new HashMap<String,List<String>>(); Map<String,List<String>> myMap = new HashMap<>(); //钻石语法 Map<String,List<String>> myMap = new HashMap(); // raw types ,会有警告,不建议这样使用
类型推断 和 泛型/非泛型的泛型构造函数
class MyClass<X> { <T> MyClass(T t) { // ... } }
new MyClass<Integer>("")
目标类型
如 Collections 有以下 静态泛型方法
static <T> List<T> emptyList();
调用:
List<String> listOne = Collections.emptyList(); // 则实例 List<String> 为 target type List<String> listOne = Collections.<String>emptyList(); // 完全写法但是 ,如果 有这样的方法
void processStringList(List<String> stringList) { // process stringList } processStringList(Collections.emptyList()); //List<Object> cannot be converted to List<String> java SE8 可以正常编译 processStringList(Collections.<String>emptyList()); //java SE 7及 之前的 JDK 版本 必须指定类型参数的值
2. 通配符 (Wildcards )
? 被称为通配符 ,代表一种未知的类型(Type),可以被用在各种情形 : 参数 、字段 、本地变量 ,有时候也会作为类型返回 。但是 ,通配符不会使用在下列的情况 : 泛型方法的类型参数 、泛型类实例的创建、或者父类型 。
2.1 上限通配符
形如 <? extends A> A 类型 或者 A 类型的子类型
栗子如下 :
public static double sumOfList(List<? extends Number> list) { // 只能Number 类型的实例或者 Number 的子类的实例 //... }
2.2 没有受限的通配符 :形如 : List<?>
如下两个场景可能会使用 :
★ 如果写 一个方法 ,该方法可以使用提供的 Object 类型
★ 泛型 类中 使用的代码不依赖于 类型参数 ,如 list.size(),即 Class<T> 不依赖于 T
栗子 :
/** * Created by xlch on 2016/7/22. */ public class Upper { public static void out(List<Object> list){ for (Object object:list){ System.out.println(object); } } public static void print(List<?> list){ for (Object object:list){ System.out.println(object); } } @Test public void test(){ List<Integer> list = Arrays.asList(1,2,3); out(list); //编译期会报错, 必须是List<Object> print(list); //编译期不会报错 } }
2.3 下限的通配符
形如 <? super A> : A 类型 或者 A 类型 的父类
<? extends A> : A 类型 或者 A 类型 的子类
2.4 通配符和子类的关系 (具有协变性)
如下图 : 虽然 Integer 是 Number 的子类 ,但是 List<Integer> 和 List<Number> 是不相关的,即泛型 他们公共父类 是 List<?>
List<String> strings = new ArrayList(); List<?> wildCards = new ArrayList<>(); List<Object> objects = new ArrayList<>(); wildCards = strings; // 通配符 ok objects = strings; // 泛型 报错 Incompatible types
2.5 通配符匹配 和 帮助方法
小栗子 :
void foo(List<?> i) { // i.set(0,i.get(0)); // 报错 helper(i); } private <T> void helper(List<T> t){ t.set(0,t.get(0)); }
3 类型 擦除 (Type)
为了实现 泛型 ,Java 编译器 运用了 类型擦除 :
★ 使用 界限类或者 Object(如果 类型参数没有界限) 替换 泛型类中所有的 类型参数 。 因此可以看作是普通的 类 / 接口 / 方法
★ 必要时 插入类型的强制转换 以保证类型的安全
类型擦除 保证 为了 参数化 类型没有 新的类创建 ,因此泛型不会产生运行时开销 。
3.1 泛型类的擦除
栗子 :
泛型类
public class Node<T> { private T data; private Node<T> next; public Node(T data,Node<T> next) } this.data = data; this.next = next; } public T getData() { return data; } // ... }擦除后的类 (没有界限,所以用 Object 替换类型参数):
@H_665_301@public class Node { private Object data; private Node next; public Node(Object data,Node next) { this.data = data; this.next = next; } public Object getData() { return data; } // ... }
3.2 泛型方法的擦除
栗子:
public static <T> int count(T[] anArray,T elem) { int cnt = 0; for (T e : anArray) if (e.equals(elem)) ++cnt; return cnt; }
擦除后:
public static int count(Object[] anArray,Object elem) { int cnt = 0; for (Object e : anArray) if (e.equals(elem)) ++cnt; return cnt; }