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方法

  1. public void setSecond(Object second) 从pair继承
  2. 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>, only Pair<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 singleInstanceprivate 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>();

    }
}

猜你喜欢

转载自blog.csdn.net/weixin_38803409/article/details/124798926