Java 泛型深度学习笔记
泛型
描述泛型的优点
使用泛型类和接口
定义泛型类和接口
解释为什么泛型类型可以提高可靠性和可读性
定义并使用泛型方法和受限泛型类型
使用原始类型向后兼容
解释为什么有必须压迫必要有通配的泛型类型
描述泛型消除并列出一些由类型消除引起的泛型上的限制和局限性
设计并实现泛型矩阵类
21.1 引言
泛型(generic)是指参数化类型的能力。
使用泛型的主要优点是能够在编译时而不是运行时检测出错误。
21.2 动机和优点
package java.lang; public interface Comparable<T> { pubic int compareTo(T o); }
这里的<T>
表示形式泛型类型(formal generic type)
随后可以用一个实际具体类型(actual concrete type)来替换它。
替换泛型类型称为泛型实例化(generic instantiation)
按照惯例,像E,T这类单个大写字母用于表示一个形式泛型类型
泛型类型必须是引用类型,
误:ArrayList<int> intList = new ArrayList<int>;
正:ArrayList<Integer> intList = new ArrayList<Integer>;
自动打包:intList.add(5),Java会自动把5包装为new Integer(5)
自动解包:int x = new Integer(5),
从泛型定义好的变量中取值时,不需要再做强制转化。
21.3 定义泛型类和接口
为了创建一个字符串堆栈,可以使用new GenericStack
这个可能会会引起对构造方法的误解:
误:pubic GenericStack<E>()
正:pubic GenericStack()
有时候,泛型类可能会有多个参数。所有的参数应以其放在尖括号中,
并用逗号隔开。例如:<E1,E2,E3>
可以定义一个类或一个接口作为泛型或者接口的子类型。
譬如在Java API中,java.lang.String类被定义为实现Comparable接口
public class String implements Comparable<String>
21.4 泛型方法
可以定义泛型接口和泛型类,还可以使用泛型类型来定义泛型方法。
为了调用泛型方法,需要将实际类型放在尖括号内作为方法名的前缀。
public class GenericMethodDemo{ public static void main(String[] args) { Integer[] integers = {1,2,3,4,5}; String[] strings = {"London","Paris","NewYork","Austin"}; GenericMethodDemo.<Integer>print(integers); GenericMethodDemo.<String>print(strings); } public static <E> void print(E[] list){ Stream.of(list).forEach { x -> {System.out.println(x);} } } }
可以将泛型指定为另一种类型的子类型。这样的泛型成为受限的(bounded)
非受限类型<E
和<E extends Object>
是一样的。
- 为了定义一个类为泛型类型,需要将泛型类型放在类名之后。
例如GenericStack<E>
, - 为了定义一个方法为泛型类型,要将泛型类型放在方法返回类型之前。
例如<E> void max <E o1, E o2>
21.5 原始类型和向后兼容
GenricStack stack = new GenericStack();
像GenericStack和ArrayList这样不使用类型参数的泛型类成为原始类型(raw type)
使用原始类型的Comparable
public class Max{ public static Comparable max (Comparable o1, Comparable o2) { if (o1.compareTo(o2) > 0) return o1; else return o2; } }
这样的代码是不安全的, 因为Max.max("Welcome",23); 编译时不会出错,但运行时会出错。
更好的编程方法就是使用泛型类型。
public class Max{ public static <E extends Comparable<E>> E max (E o1, E o2) { if (o1.compareTo(o2) > 0) return o1; else return o2; } }
E extends Comparable<E>
表示E是Comparable的子类型。
将其放在尖括号内<E extends Comparable<E>>
放在函数的前面,表示定义这个方法为泛型类型。
E max (E, E),表示这个函数入力为两个E型,返回值也为E型。
21.6 通配泛型
public static double max(Generic<Number> stack){} public static void main(String[] args) { GenericStack<Integer> intStack = new GenericStack<Integer>(); }
这样会出编译错误。因为GenericStack<Integer>
不是GenericStack<Number>
的实例
通配泛型有3种形式,?,? extends T,? super T
第一种形式 ?称为非受限通配(unbounded wildcard),
它和? extends Object是一样的
第二种形式? extends T 成为受限通配(bounded wildcard),
表示T或者T的一个未知子类型。
第三种形式? super T 称为下限通配(lower-bounded wildcard)
表示T或者T的一个未知父类型。
使用下面的代码就可以修复上面的错误。
public static double max(Generic<E extends Number> stack){}
尽管Integer是Object的一个子类型,但是
GenericStack<Integer>
并不是GenericStack<Object>
的子类型
什么时候需要<? super T>
通配符
public static void main(String[] args) { GenericStack<String> stack1 = new GenericStack<String>(); GenericStack<Object> stack2 = new GenericStack<Object>(); stack2.push("Java"); stack2.push(2); stack1.push("Sun"); add(stack1,stack2); print(stack2); } public static <T> void add(GenericStack<T> stack1, GenericStack<? super T> stack2){ while (!stack1.isEmpty()) stack2.push(stack1.pop()); }
如果用<T>
代替<? super T>
那么在add处会产生编译错误。
因为stack1的类型为GenericStack
21.7 消除泛型和对泛型的限制
泛型是使用一种称为类型消除(type erasure)的方法来实现的。
编译器使用泛型类型信息来编译代码,但是随后会消除它。
因此泛型信息在运行时是不可用的。
这种方法可以使泛型代码向后兼容使用原始类型的遗留代码。
尽管在编译时ArrayList
但运行时只有ArrayList类会被加载到JVM中。
所以下面的结果都是true。
ArrayList<String> list1 = new ArrayList<String>(); ArrayList<String> list2 = new ArrayList<String>(); System.out.println(list1 instanceof ArrayList); System.out.println(list2 instanceof ArrayList);
由于泛型类型在运行时会被消除,因此会有一些限制。
限制1 不能使用new E()
出错的原因是运行时执行new E(),但E在运行时已经是不存在的了。
限制2 不能使用new E[]
不能使用泛型类型创建数组。譬如下面的代码就是错误的。
E[] elements = new E[capacity];
可以通过创建一个Object类型的数组,然后将它的类型转换成E[]来规避这个限制。
E[] elements = (E[])new Object[capacity];
会导致下面的编译警告:
型の安全性: Object[] から E[] への未検査キャスト
需要加上 @SuppressWarnings("unchecked")
不允许使用泛型类创建泛型数组,譬如下面代码是错误的。
ArrayList<String>[] list = new ArrayList<String>[10];
会导致下面的编译错误:
ArrayList<String> の総称配列を作成できません
可以使用下面的代码来规避这种限制
ArrayList<String>[] list = (ArrayList<String>[])new ArrayList[10];
限制3 在静态环境下不允许蕾的参数是泛型类型
由于泛型类的所有实例都有相同的运行时类,所以泛型类的静态变量和方法是被它的所有实例所共享的。
因此在静态方法,数据域或者初始化语句中,为了类而引用泛型类型参数是非法的。
public static void main(E 01) { //非法 } public static E o1; //非法 static { E o1; //非法 }
限制4 异常类不能是泛型
public class MyException<T> extends Excpetion{ } try { ... } catch (MyException<T> ex){ ... }
因为如果允许这么做,就应为MyException
JVM必须检查这个从try子句中抛出的异常以确定它是否与catch子句中指定的类型匹配。
但这是不可能的,运行时类型信息是不出现的。