Java基础 --- 泛型 Generics
为什么需要泛型
- 在没有泛型类之前, Java用继承解决泛型问题. 也就是利用Object类
// before generic classes
public class ArrayList {
private Object[] elementData
. . .
public Object get(int i) {
. . . }
public void add(Object o) {
. . . }
}
- 这样有两个问题
- 当获取值时必须要转型
ArrayList files = new ArrayList();
String filename = (String) files.get(0);
- 没有error checking, 可以给这个arraylist加入任何类(因为所有类的父类都是object)
files.add(new File(". . ."));
This call compiles and runs without error. Elsewhere, casting the result of get to a String will cause an error
- 泛型提供了type parameter, 这样就可以给泛型指定一个类型, 代码更易读, 不用转型并且有error checking
ArrayList<String> files = new ArrayList<String>();
泛型
泛型类
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null; second = null; }
public Pair(T first, T second) {
this.first = first; this.second = second; }
public T getFirst() {
return first; }
public T getSecond() {
return second; }
public void setFirst(T newValue) {
first = newValue; }
public void setSecond(T newValue) {
second = newValue; }
}
泛型方法
class ArrayAlg {
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
}
Bounds for Type Variable
class ArrayAlg {
public static <T> T min(T[] a) // almost correct {
if (a == null || a.length == 0) return null;
T smallest = a[0];
for (int i = 1; i < a.length; i++)
if (smallest.compareTo(a[i]) > 0) smallest = a[i];
return smallest;
}
}
- 上面的代码, T可以是任何类, 但是.
compareTo
方法只有实现了Comparable
接口才有- 如何确保 T 一定实现了
Comparable
接口
- 可以给type variable限定bounds, 也就是限定T必须继承某个父类或者接口
public static <T extends Comparable> T min(T[] a)
- 也可以限定多个接口
T extends Comparable & Serializable
- 虽然bound可以是接口也可以是类, 但是最多只能限定一个类, 并且必须放在bounds list的第一个. 接口可以限定多个
class ArrayAlg
{
/**
Gets the minimum and maximum of an array of objects of type T.
@param a an array of objects of type T
@return a pair with the min and max value, or null if a is
null or empty
*/
public static <T extends Comparable> Pair<T> minmax(T[] a)
{
if (a == null || a.length == 0) return null;
T min = a[0];
T max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min, max);
}
}
Java虚拟机如何处理泛型 — 泛型擦除
泛型擦除 — Type Erase
- Java 中的泛型只有在编译阶段存在,在代码运行的时候是没有泛型的,这也被称为泛型擦除
- 虚拟机会对泛型代码进行泛型擦除, 也就是将所有的类型 T 替换成raw type, raw type就是bound list中的第一个类型, 或者是Object类型 如果没有bounds list
无bound list的情况
//执行完Type Erasure的代码
public class Pair
{
private Object first;
private Object second;
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first; }
public Object getSecond() {
return second; }
public void setFirst(Object newValue) {
first = newValue; }
public void setSecond(Object newValue) {
second = newValue; }
}
有bound list的情况
public class Interval<T extends Comparable & Serializable> implements Serializable
{
private T lower;
private T upper;
. . .
public Interval(T first, T second)
{
if (first.compareTo(second) <= 0) {
lower = first; upper = second; }
else {
lower = second; upper = first; }
}
}
public class Interval implements Serializable
{
private Comparable lower;
private Comparable upper;
. . .
public Interval(Comparable first, Comparable second) {
. . . }
}
类型转换
Pair<Employee> buddies = . . .;
Employee buddy = buddies.getFirst();
- 经过泛型擦除之后buddies.getFirst返回的是Object类型, 编译器自动进行类型转换, 转为Employee类型
桥接方法 — bridge Method
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null; second = null; }
public Pair(T first, T second) {
this.first = first; this.second = second; }
public T getFirst() {
return first; }
public T getSecond() {
return second; }
public void setFirst(T newValue) {
first = newValue; }
public void setSecond(T newValue) {
second = newValue; }
}
class DateInterval extends Pair<LocalDate>
{
public void setSecond(LocalDate second)
{
if (second.compareTo(getFirst()) >= 0)
super.setSecond(second);
}
. . .
}
泛型擦除之后
class DateInterval extends Pair // after erasure
{
public void setSecond(LocalDate second) {
. . . }
. . .
}
这样DataInterval会有两个setSecond方法
public void setSecond(Object second)
从pair继承public void setSecond(LocalDate second)
这是两个完全不同的方法, 因为参数不一样. 但是实际上应该是一个方法, DataInterval应该Override从pair继承的setSecond方法.
DateInterval interval = new DateInterval(. . .);
Pair<LocalDate> pair = interval; // OK--assignment to superclass
//pair的声明类型为Pair所以会调用public void setSecond(Object second)
//而无法调用 public void setSecond(LocalDate second)
pair.setSecond(aDate);
为了解决这个问题, 编译器会在DataInterval
类自动生成一个bridge method
public void setSecond(Object second) {
// 调用 public void setSecond(LocalDate second)
setSecond((LocalDate) second);
}
当下面代码被调用时, pair.setSecond会调用bridge method, 然后bridge method调用实际的setSecond方法
DateInterval interval = new DateInterval(. . .);
Pair<LocalDate> pair = interval; // OK--assignment to superclass
pair.setSecond(aDate);
In summary, you need to remember these facts about translation of Java generics:
- There are no generics in the virtual machine, only ordinary classes and
methods.- All type parameters are replaced by their bounds.
- Bridge methods are synthesized to preserve polymorphism.
- Casts are inserted as necessary to preserve type safety
Restrictions and Limitations
Type Paramter Cannot be Instantiated with Primitive Types
- there is no
Pair<double>
, onlyPair<Double>.
- 因为泛型擦除之后, Pair的类型是Object, double不是Object的子类
在运行时进行的类型查询只适用于原始类型(擦除之后的类型)
- 对类型的查询只能使用Raw Type (擦除之后的类型)
if (a instanceof Pair<String>) // Error
if (a instanceof Pair<T>) // Error
//getClass method 返回的也是raw type
Pair<String> stringPair = . . .;
Pair<Employee> employeePair = . . .;
if (stringPair.getClass() == employeePair.getClass()) // they are equal
Java 不支持泛型数组
在Java中, 数组会记住元素的类型, 如果试图储存其他类型的元素, 就会抛出一个ArrayStoreException
- 但是对于泛型类型, 擦除会使这种机制无效. 所以Java 不支持泛型数组
Pair<String>[] table = new Pair<String>[10];
Object[] objarray = table;
//数组中应该存入Pari<String>类型, 但是下面这行代码可以通过array store exception check因为擦除之后的类型是Object
//最终会在运行时报错,
objarray[0] = new Pair<Employee>();
- 可以声明通配类型的数组, 然后进行类型转换, 但是结果将是不安全的
Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];
If you store a Pair in table[0] and then call a String
method on table[0].getFirst(), you get a ClassCastException.
不能实例化类型变量
You cannot use type variables in an expression such as new T(…).
For example, the following Pair constructor is illegal:
public Pair() {
first = new T(); second = new T(); } // Error
Type Variable 不能用static关键字修饰
public class Singleton<T>
{
private static T singleInstance; // Error
public static T getSingleInstance() // Error
{
if (singleInstance == null) construct new instance of T
return singleInstance;
}
}
- 如果允许声明泛型类的static方法或者属性, 则一个程序可以声明两个类比如
Singleton<Random>
,Singleton<JFileChooser>
, 但是泛型擦除之后, 只有一个Singleton类和singleInstance属性. 则private static Random singleInstance
和private static JFileChooser singleInstance
冲突, 所以不行
泛型类不能继承Throwable接口
public class Problem<T> extends Exception {
/* . . . */ } // Error--can't extend Throwable
不能在catch中使用泛型
public static <T extends Throwable> void doWork(Class<T> t)
{
try
{
do work
}
catch (T e) // Error--can't catch type variable
{
Logger.global.info(...)
}
}
但是可以限定type variable继承Throwable接口
public static <T extends Throwable> void doWork(T t) throws T // OK
{
try
{
do work
}
catch (Throwable realCause)
{
t.initCause(realCause);
throw t;
}
}
注意擦除后的冲突
- 比如将如下代码加入Pair class
public class Pair<T>
{
public boolean equals(T value) {
return first.equals(value) && second.equals(value); }
. . .
}
- 则会出现两个equals method, 解决方法是重新命名冲突的方法
boolean equals(String) // defined in Pair<T>
boolean equals(Object) // inherited from Object
泛型中的继承
- 如果Manager是Employee的子类, 那么
Pair<Manager>
是Pair<Employee>
的子类吗? 答案是"NO"- 所以以下代码是错的, 不能把
Pair<Manager>
赋给Pair<Employee>
Manager[] topHonchos = . . .;
Pair<Employee> result = ArrayAlg.minmax(topHonchos);
通配符类型 — Wildcard Types
类型通配符的分类
- 类型通配符:<?> List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中- 类型通配符上限:<? extends 类型>
List<? extends Number>:它表示的类型是Number或者其子类型(也就是最大的就是Number类型)- 类型通配符下限:<? super 类型>
List<? super Number>:它表示的类型是Number或者其父类型(也就是最小的就是Number类型)
public class GenericDemo {
public static void main(String[] args) {
//类型通配符:<?>
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
System.out.println("--------");
//类型通配符上限:<? extends 类型>
//上线Number就是做大的,所以Object不行
// List<? extends Number> list4 = new ArrayList<Object>();
List<? extends Number> list5 = new ArrayList<Number>();
List<? extends Number> list6 = new ArrayList<Integer>();
System.out.println("--------");
//类型通配符下限:<? super 类型>
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list8 = new ArrayList<Number>();
//下线就是Number最小的,所以其子类,Interger不行
// List<? super Number> list9 = new ArrayList<Integer>();
}
}