Skip to main content
Jkyo Chen Blog

第十二章 泛型程序设计

泛型程序设计 #

为什么要使用泛型程序设计 #

定义简单泛型类 #

package pair1;

public class PairTest1 {
	public static void main(String[] args) {
		String[] words = { "Mary", "had", "a", "little", "lamb"};
		Pair mm = ArrayAlg.minmax(words);
		System.out.println("min = " + mm.getFirst());
		System.out.println("max = " + mm.getSecond());
	}
}

class ArrayAlg {
	public static Pair minmax(String[] a) {
		if(a == null || a.length == 0) return null;
		String min = a[0];
		String 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);
	}
}

泛型方法 #

class ArrayAlg {
	public static  T getMiddle(T... a) {
		return a[a.length / 2];
	}
}
//当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
String middle = ArrayAlg.getMiddle("John", "Q.", "Public");

//在大多数情况下,方法调用中可以省略类型参数。编译器有足够的信息能够推断出所调用的方法。
//它用names的类型(String[])与泛型类型T[]进行匹配并推断出T一定是String
String middle = ArrayAlg.getMiddle("John", "Q.", "Public");

//编译器也会提示报错
double middle = ArrayAlg.getMiddle(3.14, 1729, 0);
//错误消息会以晦涩的方式指出(不同的编译器给出的错误消息可能有所不同):解释这句代码有两种方法,而且这两种方法都是合法的。简单地说,编译器将会自动打包参数为1个Double和2个Integer对象,而后寻找这些类的共同超类型。事实上,找到2个这样的超类型:Number和Comparable接口,其本身也是一个泛型类型。在这种情况下,可以采取的补救措施是将所有的参数写为double值。

类型变量的限定 #

泛型代码和虚拟机。 #

翻译泛型表达式 #

翻译泛型方法 #

调用遗留代码 #

约束与局限性 #

不能用基本类型实例化类型参数 #

运行时类型查询只适用于原始类型 #

不能创建参数化类型的数组 #

Varargs警告 #

不能实例化类型变量 #

泛型类的静态上下文中类型变量无效 #

不能抛出或捕获泛型类的实例 #

注意擦除后的冲突 #

泛型类型的继承规则 #

通配符类型 #

Pair mangerBuddies = new Pair<>(ceo, cfo);
Pair wildcardBuddies = managerBuddies;//OK
wildcardBuddies.setFirst(lowlyEmployee);//compile-time error

//对setFirst的调用有一个类型错误。要了解其中的缘由,看一看类型Pair
? extends Employee getFirst()
void setFirst(? extends Employee)

//这样就不能调用setFirst方法。编译器只知道需要某个Employee的子类型,但不知道具体是什么类型。它拒绝传递任何特定的类型。毕竟?不能用来匹配
//使用getFirst就不存在这个问题:将getFirst的返回值赋给一个Employee的引用完全合法。
//这就是引入有限定的通配符的关键之处。现在已经有办法区分安全的访问器方法和不安全方法的更改器方法了。

通配符的超类型限定 #

? super Manger
//这个通配符限制为Manager的所有超类型

//带有超类型限定的通配符的行为与上面的限定相反。
//可以为方法提供参数,但不能使用返回值。

//例如,Pair 有方法
void setFirst(? super Manager)
? super Manager getFirst()

//编译器步骤setFirst方法的确切类型,但是可以用任意Manager对象(或子类型)调用它,而不能用Employee对象调用。然而,如果调用getFirst,返回的对象类型就不会得到保证。只能把它赋给一个Object

无限定通配符 #

//例如,Pair 初看起来,这好像与原始的Pair类型一样。实际上有很大的不同。类型Pair有方法:
? getFirst()
void setFirst(?)

//getFirst的返回值只能赋值给一个Object。setFirst方法不能被调用,甚至不能用Object调用。
//Pair和Pair本质的不同在于:可以用任意Object对象调用原始的Pair类的setObject方法。
//可以调用setFirst(null)

//测试一个pair是否包含一个null引用,它不需要实际的类型
public static boolean hasNulls(Pair p) {
	return p.getFirst() == null || p.getSecond() == null;
}

//通过将hasNulls转换成泛型方法,可以避免使用通配符类型:
public static  boolean hasNulls(Pair p)

//但,带有通配符的版本可读性更强。

通配符捕获 #

//编写一个交换一个pair元素的方法:
public static void swap(Pair p)
//通配符不是类型变量,因此不能在编写代码中使用"?"作为一种类型。

? t = p.getFirst();//ERROR
p.setFirst(p.getSecond());
p.setSecond(t);

//在交换时必须要保存第一个元素。辅助方法swapHelper
public static  void swapHelper(Pair p) {
	T t = p.getFirst();
	p.setFirst(p.getSecond());
	p.setSecond(t);
}
//注意swapHelper是一个泛型方法。而swap不是,它具有固定的Pair类型的参数

public static void swap(Pair p) { swapHelper(p); }
//在这种情况下,swapHelper方法的参数T捕获通配符。它不知道时哪种类型的通配符,但是,这是一个明确的类型,并且swapHelper的定义只有在T指出类型时才有明确的含义

//也可以用没有通配符的泛型方法
 void swap(Pair p)

public static void maxminBonus(Manager[] a, Pair result) {
	minmaxBonus(a, result);
	PairAlg.swap(result);//OK--swapHelper captures wildcard type
}
//通配符捕获机制是不可避免的。
package pair3;

public class PairTest3{
	public static void main(String[] args) {
		Manager ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
		Manager cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
		Pair buddies = new Pair<>(ceo, cfo);
		printBuddies(buddies);

		ceo.setBonus(1000000);
		cfo.setBonus(500000);
		Manager[] managers = { ceo, cfo};

		Pair result = new Pair<>();
		minmaxBonus(managers, result);
		System.out.println("first:" + result.getFirst().getName() + ", second:" + result.getSecond().getName());
	}

	public static void printBuddies(Pair p) {
		Employee first = p.getFirst();
		Employee second = p.getSecond();
		System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
	}

	public static void minmaxBonus(Manager[] a, Pair result) {
		if(a == null || a.length == 0) return;
		Manager min = a[0];
		Manager max = a[0];
		for(int i = 1; i < a.length; i++) {
			if(min.getBonus() > a[i].getBonus()) min = a[i];
			if(max.getBonus() < a[i].getBonus()) max = a[i];
		}
		result.setFirst(min);
		result.setSecond(max);
	}

	public static void maxminBonus(Manager[] a, Pair result) {
		minmaxBonus(a, result);
		PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type
	}
}

class PairAlg{
	public static boolean hasNulls(Pair p) {
		return p.getFirst() == null || p.getSecond() == null;
	}

	public static void swap(Pair p) { swapHelper(p); }

	public static  void swapHelper(Pair p) {
		T t = p.getFirst();
		p.setFirst(p.getSecond());
		p.setSecond(t);
	}
}

反射和泛型 #