泛型小结

来源:互联网 发布:阿里云是laas还是paas 编辑:程序博客网 时间:2024/04/30 00:16

参考书籍:《Java Generics and Collections》  authors:Maurice Naftalin,Philip Wadler

                  《Effective Java》                            author  :Joshua Bloch      译者:杨春花,俞黎敏

在没有泛型之前,从集合中读取的每一个对象都必须进行转换,如果不小心插入类型错误的对象,在运行期间对象转换的时候就会出错。有了泛型之后就可以告诉编译器集合能接受哪些类型的对象,在插入的时候进行自动转换,对象转换出错时及时报错。

每个类型都定义了parameterized type参数化类型和raw type原生类型,如List<String>,List便是原生类型,String便是参数化类型。

一些术语

Wildcards(通配符)

下面这个例子介绍了带有extends和super的wildcards的用法即通配符的两种形式:? extends E和? super E,另外只写?表示? extends Object的简写。

package com.company.collections.subtypingwildcards;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Iterator;import java.util.List;/** * wildcards通配符和subtyping子类型 * 接口Collection有如下方法 *    interface Collection<E> {  ...//"? extends E" ?局势wildcards通配符表示任何一种类型,这里表示E的任何一种子类型  public boolean addAll(Collection<? extends E> c);  ...  }  类Collections如下方法  // ? super T 表示src的元素师T的subtype,dst是T的supertype  public static <T> void copy(List<? super T> dst, List<? extends T> src) {  for (int i = 0; i < src.size(); i++) {    dst.set(i, src.get(i));  }} * */public class SubtypingWildcards {public static void main(String[] args) {//Wildcards with extends例子如下List<Number> nums = new ArrayList<Number>();List<Integer> ints = Arrays.asList(1, 2);List<Double> dbls = Arrays.asList(2.78, 3.14);//List<Number>是Collection<? extends Number>的子类型 //List<Integer>是Collection<? extends Number>的子类型,故ints可添加,dbls类似nums.addAll(ints);nums.addAll(dbls);//List<Integer> ints = Arrays.asList(1,2);//List<? extends Number> nums = ints;//because List<Integer> is a subtype of List<? extends Number>,没错//nums.add(3.14);  // compile-time error只能存放Integer,不解释//Wildcards with super例子如下List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");List<Integer> ints2 = Arrays.asList(5, 6);Collections.copy(objs, ints2);for (Iterator iterator = objs.iterator(); iterator.hasNext();) {Object object = (Object) iterator.next();System.out.println(object.toString());}}}


通配符什么时候使用,以及使用中需要注意的地方,例子如下(主要介绍的是The Get and Put Principle取出存取法则

package com.company.collections.subtypingwildcards;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.List;/** *什么时候用wildcards通配符?用? extends E还是? super E ? * *GetPut法则:从数据结构中取出数据用extends,存入数据用super,取和存都需要的时候就不用通配符 *            If an extends wildcard  is present, 那就只能取数据 *            if a  super    wildcard is present, 那就只能存数据 *很好理解,取数据时,写了一方法参数有? extends E那么E的所有子类包括自己都能调用方法取数据 *         存数据时,写了一方法参数有? super E那么存的时候不仅可以存E类型的,还可以存E的超类 * * */public class GetPutPrinciple {public static void main(String[] args) {//取数据  ? extends EList<Integer> ints = Arrays.asList(1,2,3); System.out.println(sum(ints));List<Double> doubles = Arrays.asList(2.78,3.14); System.out.println(sum(doubles));List<Number> nums = Arrays.<Number>asList(1,2,2.78,3.14); System.out.println(sum(nums));//存数据 ? super E List<Integer> ints2 = new ArrayList<Integer>(); count(ints2, 5); System.out.println(ints2.toString()); //要是不用super,下面这两个调用就是非法的了 List<Number> nums2 = new ArrayList<Number>(); count(nums2, 5);  nums2.add(5.0); System.out.println(nums2.toString()); List<Object> objs = new ArrayList<Object>(); count(objs, 5);  objs.add("five"); System.out.println(objs.toString());//既要取数据又要存数据,不用通配符? List<Number> nums3 = new ArrayList<Number>(); System.out.println(sumCount(nums3,5));  // List<Integer> ints4 = Arrays.asList(1,2,3);// List<? extends Number> nums4 = ints;// double dbl = sum(nums4);  // ok// nums4.add(3);  // compile-time error // List<Object> objs5 = Arrays.<Object>asList(1,"two");// List<? super Integer> ints5 = objs;// ints5.add(3);  // ok// double dbl5 = sum(ints5);  // compile-time error}public static double sum(Collection<? extends Number> nums) {double s = 0.0;for (Number num : nums) s += num.doubleValue();return s;}public static void count(Collection<? super Integer> ints, int n) {  for (int i = 0; i < n; i++) ints.add(i);}public static double sumCount(Collection<Number> nums, int n) {  count(nums, n);  return sum(nums);}}

与Array比较

区别一:

在书《Java Generics and Collections》中有讲到:array  subtyping is covariant,the subtyping relation for generics  is invariant。意思是说数组子类是协变量,而泛型不是,也就是,对于数组S[] 和T[],只要S是T的子类型,那么S[]便是T[]的子类型,叫协变,而泛型List<S>和List<T>,若S是T的子类型,但List<S>不是List<T>的子类型,二者相对独立。

区别二:

数组是具体化的(reified),因此数组在运行时才会知道并且检查元素类型约束,而泛型是通过擦除(erasure),所以编译期值强化他们的类型信息,在运行的时候丢弃元素类型信息,擦除可以让泛型与没有使用泛型的代码任意互用。在运行时,每个泛型类只有一种类型. 具体地说, List<Integer>, List<String> 和 List<List<String>> 在运行时都将具有相同的类型: List。

看下面例子

Integer[] ints3 = new Integer[] {1,2,3};Number[] nums3 = ints3;nums3[2] = 3.14;  // array store 运行时报错,编译没问题

 

List<Integer> ints1 = Arrays.asList(1,2,3);List<Number> nums1 = ints1; // compile-time errornums1.add(2, 3.14);

数组的例子很好理解,nums3实际是Integer类型,加个double类型数据当然报错,nums3是在运行时动态绑定为Integer的,故编译器没错。而泛型呢,编译期就报错,因为List<Integer>不是List<Number>的子类型,Integer是Number的子类型不管用,泛型不是协变的。

实际上,我要说的的是泛型将数组运行期类型检查提前到了编译期,好处就是编译期类型检查了,运行期就没必要检查了,更加高效,另外若有错误编译期间能即时报错。

另外,泛型的方法比数组的多,数组就是存取。但是,数组毕竟比泛型效率更高,因为数组不像泛型要装箱(boxing)。

使用数组的唯一理由是: 大量的原始数据类型, 可能可以获得性能上的提升. 不过一定要谨记: 不要优化你的程序, 除非经过严格而精确的测量证明存在性能问题. 另外, 有些情况下, 由于某些遗留系统的兼容问题, 你可能仍需要使用数组.所以,尽量使用泛型而不是数组。


擦除

前面已经解释了擦除。下面举几个例子说明一下。

以下内容引用自http://zy19982004.iteye.com/blog/1977055

使用擦除的核心动机:使得泛型化的客户端代码可以使用非泛型化的类库,非泛型化的客户端代码可以使用泛型化的类库。这个被称为“兼容迁移性”。这也从侧面反应了,前期的设计多么重要,倘若JDK1.0就将泛型纳入其中,必将是Java使用者的一大福音。

package com.jyz.study.jdk.generic;import java.util.ArrayList;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;/** * 在泛型代码内部,无法获得任何有关泛型参数类型的信息 * @author JoyoungZhang@gmail.com * */public class ClassTypeParameters {static class Frob<T>{}static class FrobF<T extends Number>{}static class FrobPM<P,M>{}private static List list1 = new ArrayList();private static List<Integer> list2 = new ArrayList<Integer>();private static Map<Integer, Integer> map1 = new HashMap<Integer, Integer>();static Frob f1 = new Frob();static FrobF<Integer> f2 = new FrobF<Integer>();static FrobPM<Integer, Double> f3 = new FrobPM<Integer, Double>();//Calss.getTypeParameters()将返回一个TypeVariable对象数组//表示有泛型声明所声明的形式类型参数public static void main(String[] args) {System.out.println(Arrays.toString(list1.getClass().getTypeParameters()));System.out.println(Arrays.toString(list2.getClass().getTypeParameters()));System.out.println(Arrays.toString(map1.getClass().getTypeParameters()));System.out.println(list1.getClass().getSimpleName());System.out.println(list2.getClass().getSimpleName());System.out.println(Arrays.toString(f1.getClass().getTypeParameters()));System.out.println(Arrays.toString(f2.getClass().getTypeParameters()));System.out.println(Arrays.toString(f3.getClass().getTypeParameters()));}}输出结果[E][E][K, V]ArrayListArrayList[T][T][P, M]

 

 

擦除原则:

  1. 无限定的形式类型参数将被替换为Object。比喻List<T> T是无限定的,被替换为Object。
  2. 有限定的形式类型参数将被替换为第一个限定类型。比喻List<T extends Comparable & Serializable>,T被替换为Comparable,也称T被擦除到了Comparable。

 

 

0 0
原创粉丝点击