泛型笔记
1.泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。
2. 在Java中在没增加泛型之前:
ArrayList array = new ArrayList();
array.add(100);
array.add("dfjd");
无论是在编译时,还是在运行时,该代码都不会出错,因为ArrayList类在Java没设置泛型之前,所添加的类型为:Object
在原始的ArrayList类中会出现两个问题:
-
取一个值时,必须进行强制转换
String s = (String)array.get(1);
2.String s = (String)array.get(0); //因为get(0)中的值为int类型,但int 与String类型之间不能进行直接的转换,所以会报错
3.解决方式----类型参数
在ArrayList类有一个类型参数用来指示元素的类型:
ArrayList<String> array = new ArrayList<String>(); //这就告诉,array对象中只能存放String类型的元素
构造函数中可以省略泛型类型:
ArrayList<String> array = new ArrayList<>(); //因为编译器能够推断出
类型参数的魅力:使得程序具有更好的可读性和安全性
4.定义简单的泛型
类
(1). 一个泛型类就是具有一个或多个类型变量的类
public
class Pair
<T> { //在类名之后使用<>将参数类型T括起来,这个普通的类就变成了泛型类
private T
first;
private T
second;
public Pair(T
first,T
second){ //当实例化Pair对象时,指定类类型,则T会替换成该类类型
this.
first =
first;
this.
second =
second;
}
public T getFirst() {
System.out.println("这是父类中的getFirst方法");
return
first;
}
public
void setFirst(T
first) {
this.
first =
first;
}
public T getSecond() {
return
second;
}
public
void setSecond(T
second) {
System.out.println("这是父类中的setSecond方法");
this.
second =
second;
}
@Override
public String toString() {
return
"Pair类中的first的值为:" +
this.getFirst() +
'\n' +
"Pair类中的second的值为:" +
this.getSecond();
}
}
(2).具有多个类型变量的泛型类
public class Pair
<T,U>{
.....
}
(3).
泛型类的作用:类定义中的类型变量指定
方法的返回类型以及
域或者
局部变量的类型
(4).类型变量使用大写形式,且比较短,在Java中,使用E表示集合的元素类型,K和V分别表示表的关键字与值的类型,普通的泛型类的参数类型一般是"T"
(5).在实例化泛型类的时候要指定类型用来代替参数类型
如: Pair<String> p = new Pair<>("123","456"); //传入的实参类型必须与指定的参数类型一致,即为String
即: Pair类中的域,构造方法,get/set方法中的T都用String代替了
注意:
1).用来代替参数类型的类型必须是类类型,不能是基本类型(如int,double等)
2).不能使用instanceof对参数类型进行类型的检验 如: T instanceof String (这样是错误的)
实例:
public
class PairFunctin {
public
static Pair<String> minMax(String[]
a){ //Pair类中的泛型类被String类替代
if(
a ==
null ||
a.
length ==0){
return
null;
}
String
min =
a[0];
String
max =
a[0];
for (
int
i = 0;
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<String>(
min,
max);
}
}
public
class PairTest {
public
static
void main(String[]
args) {
String[]
strings = {
"dfdfd",
"reiuuu",
"wuee",
"oerooi",
"fwpjsj"};
Pair<String>
pair = PairFunctin.
minMax(
strings);
System.
out.println(
pair.toString()); //Pair类中重写了toString方法
}
}
结果为:
Pair类中的first的值为:dfdfd
Pair类中的second的值为:wuee
5.泛型
方法
注意:上面的Pair泛型类中的构造方法,get/set方法都不是泛型方法,因为泛型方法有其自身定义的语法
定义泛型方法
public class PairFunction[<T>] {
public [static] <T,[U]> T/[U] getStaticMiddle(T[] a){
....
}
}
(1).public class PairFunction[<T>]-----说明泛型方法可以定义在普通类或者泛型类中
(2).public [static] <T,[U]> T/[U]/[类类型] getStaticMiddle(T[] a)
1).泛型方法的类型变量(这里是<T,[U]>,[U]表面U是可选的,即一个泛型方法可以带有多个类型变量)放在修饰符(这里是public [static]) 的后面,返回类型(这里是T/[U]/[类类型])的前面
2).说明静态或者普通的方法都可以定义成泛型方法
3).public [static]
<T> T getStaticMiddle(
U[] a)---此方法会报错,因为U没有在定义泛型方法时进行声明,即未在修饰符(public [static])和返回类型(T)之间声明,
未声明的类型变量不能够使用,使用必须声明
4).与泛型类的参数类型T的定义一样,此处的T可以随便写入为任意字符(如U)
(3).在普通类中使用泛型方法
实例:
/*
* 普通方法中使用泛型方法
*/
@SuppressWarnings(
"unchecked")
public <T> T getMiddle(T[]
a){
if(
a ==
null ||
a.
length == 0){
return
null;
}
String
string =
"";
//string = a.toString();时,结果是Object--->String,说明在编译时,就进行了参数类型擦除
for (
int
i = 0;
i <
a.
length;
i++) {
string +=
a[
i];
}
return (T)
string;
}
/*
* 静态方法中使用泛型方法---下面使用的形式,在普通方法中一样可以使用,不在一一说明
*/
public
static <T> String getStaticMiddle(T[]
a){ //传入的类型与返回的类型参数一样
if (
a ==
null ||
a.
length == 0) {
return
null;
}
String
string =(String)
a[0];
return
string;
}
public
static <T> Integer getStaticMiddle1(T[]
a){ //返回类型可以与传入的类型不一致
if(
a ==
null ||
a.
length == 0){
return
null;
}
Integer
integer = Integer.
valueOf((String)
a[1]);
return
integer;
}
@SuppressWarnings(
"unchecked")
public
static <T> T getStaticMiddle2(T[]
a){
if(
a ==
null ||
a.
length == 0){
return
null;
}
String
string =
"";
//string = a.toString();时,结果是Object--->String,说明在编译时,就进行了参数类型擦除
for (
int
i = 0;
i <
a.
length;
i++) {
string +=
a[
i];
}
return (T)
string; //强制转化为T,运行时,编译器会自动识别其类型
}
@SuppressWarnings(
"unchecked")
public
static <T,U> U getStaticMiddle3(T[]
a){
if(
a ==
null ||
a.
length == 0){
return
null;
}
Integer
integer = Integer.
valueOf((String)
a[1]);
return (U)
integer; //返回类型为U,将Integer类型强制转化为U,但在调用此方法时,System.out.println会报错,因为println不知道输入的是什么类型的PrintStream
}
@SuppressWarnings(
"unchecked")
public
static <T> T getStaticMiddle4(String[]
a){
if(
a ==
null ||
a.
length == 0){
return
null;
}
Integer
integer = Integer.
valueOf((String)
a[1]);
return (T)
integer;
}
5).泛型方法,是在调用方法的时候指明泛型的具体类型
PairFunctin
pairFunctin =
new PairFunctin();
System.
out.println(
pairFunctin.getMiddle(
strings).toString());
System.
out.println(PairFunctin.
getStaticMiddle(
strings));
System.
out.println(PairFunctin.
getStaticMiddle1(
strings));
System.
out.println(PairFunctin.
getStaticMiddle2(
strings));
System.
out.println((Integer)PairFunctin.
getStaticMiddle3(
strings)); //所以,这里调用的时候,还是需要强制转化为Integer
System.
out.println((Integer)PairFunctin.
getStaticMiddle4(
strings));
结果为:
dfdfd123wueeoerooifwpjsj
dfdfd
123
dfdfd123wueeoerooifwpjsj
123
123
(4).在泛型类中使用泛型方法
public
class GenericityClass<T> {
/*
*注意:
* 在静态方法中使用泛型类中的参数类型T,会报错,所以,在静态方法中使用类型参数,该静态方法必须为泛型方法
* 这是因为静态方法无法访问类上定义的泛型
public
static
void getStaticMiddle5(
T[] a){ //
Cannot make a static reference to the non-static type T
......
}
*/
@SuppressWarnings(
"unchecked")
public <U> T getMiddle1(U[]
a){
//U与T可以为同一类型,也可以为不同类型
if(
a ==
null ||
a.
length == 0){
return
null;
}
Integer
integer = Integer.
valueOf((String)
a[1]);
return (T)
integer;
//U与T不是同一类型,U为String,T为Integer
}
@SuppressWarnings({
"hiding",
"unchecked" })
public <T> T getMiddle2(T[]
a){
if (
a ==
null ||
a.
length ==0) {
return
null;
}
System.
out.println(
a.getClass().getName()); //结果为:[Ljava.lang.String;,说明自动识别了数组a为String类型
T
integer =(T)Integer.valueOf((String)
a[1]);
System.
out.println(
integer.getClass().getName()); //结果为:java.lang.Integer
return
integer; //这里结合PairTest类来讲,泛型方法中的T为String,而GenericityClass泛型类中的T为Integer,说明返回的类型与泛型方法中自动检查到的类型一致
}
@SuppressWarnings({
"unchecked",
"hiding" })
public <T,U> U getMiddle3(T[]
a){
if (
a ==
null ||
a.
length ==0) {
return
null;
}
U
integer =(U)Integer.
valueOf((String)
a[1]);
return
integer; //运行时,JVM无法判别integer为啥类型
}
}
在PairTest类中:
GenericityClass<Integer>
genericityClass =
new GenericityClass<>();
System.
out.println(
genericityClass.getMiddle1(
strings));
结果为:
123
//System.
out.println(
genericityClass.getMiddle2(
strings)); //在编译时,没有报错,在运行时,报错:
java.lang.ClassCastException
: java.lang.Integer cannot be cast to java.lang.String
System.out.println(String.valueOf(genericityClass.getMiddle2(strings)));
结果为:
123
//System.
out.
println(
genericityClass.getMiddle3(
strings)); 在编译时,就报错:
The method println(boolean) is ambiguous for the type PrintStream,即type模糊
System.out.println((Integer)genericityClass.getMiddle3(strings))
;
结果为:
123
6.类型变量的限定
有时,需要对泛型类和泛型方法中的类型变量加以约束
class ArrayAlg{
public static <T> T min(T[] a){
if(a == null || a.length == 0){
return null;
}
T smallest = a[0];
for(int i = 0; i < a.length; i++){
if(smallest.compareTo(a[i] > 0){ //比如String LocalDate实现了接口Comparable,Rectangle没有实现Comparable
smallest = a[i];
}
}
return smallest;
z }
在Java中,并不是所有的类都去实现了Comparable接口,调用compareTo方法,所以为了运行时不会报错,该泛型方法需要对其类型变量去进行约束:T extends Comparable,表明只有实现了Comparable接口的类类型,才能使用该泛型方法
即: public static < T extends Comparable> min(T[] a){....}
注意:
这里使用的是extends
(1).表示T应该是绑定类型的子类型,T和绑定类型可以是类,也可以是接口
(2).选择关键字extends的原因是更接近子类的概念
(3).一个类型变量或通配符可以有多个限定 如:T extends Comparable & Serializable,限定类型用“&”分隔,而用逗号来分隔类型变量
(4).在Java的继承中,可以根据需要拥有多个接口超类型,但限定中至多有一个类,如果用一个类做为限定,它必须是限定列表中的第一个
实例:
public
class RestrainGenericityClassOrMethod <T> {
@SuppressWarnings({
"rawtypes",
"unchecked" })
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 = 0;
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);
}
}
在PairTest类中:
LocalDate[]
birthday = {
LocalDate.
of(1992, 4, 24),
LocalDate.
of(1989, 9, 06),
LocalDate.
of(1988, 2, 11),
LocalDate.
of(1995, 3, 11),
LocalDate.
of(1966, 10, 15)
};
Pair<LocalDate>
pair2 = RestrainGenericityClassOrMethod.
minmax(
birthday);
System.
out.println(
pair2.toString());
结果为:
Pair类中的first的值为:1995-03-11
Pair类中的second的值为:1966-10-15
7.类型擦除
虚拟机没有泛型类型变量---所有对象都属于普通类
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型,原始类型的名字就是删去类型参数后的泛型类型名,擦除类型变量,并替换为限定类型(无限定类型则用Object)
如:上面的Pair类---T没有限定类型,所以它的原始类型为:
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
void setFirst(Object
first) {
this.
first =
first;
}
public ObjectT getSecond() {
return
second;
}
public
void setSecond(Object
second) {
this.
second =
second;
}
@Override
public String toString() {
return
"Pair类中的first的值为:" +
this.getFirst() +
'\n' +
"Pair类中的second的值为:" +
this.getSecond();
}
}
(1).在程序中可以包含不同类型的Pair,例如,Pair<String>或Pair<LocalDate>,而擦除类型后就变成原始的Pair类型
(2).原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Object替换 public class Pair<T extends Comparable & Serializable>{.....},则原始类型为:
public class Pair{
private Comparable
first;
private Comparable
second;
public Pair(Comparable
first,Comparable
second){
this.
first =
first;
this.
second =
second;
}
public Comparable getFirst() {
return
first;
}
....
}
(3).如果泛型类为:public class Pair<Serializable & Comparable>{...},则原始类型为:
public class Pair{
private Serializeable
first;
private Serializeable
second;
public Pair(Serializeable
first,Serializeable
second){
this.
first =
first;
this.
second =
second;
}
public Serializeable getFirst() {
return
first;
}
....
}
注意:编译器在必要时要向Comparable插入强制类型转换,为了提供效率,应该将标签接口(没有方法的接口)放在边界列表的末尾
8.翻译泛型表达式
在上面的PairTest类中---Pair类中的参数类型T未有限定,即其原始类型为:T被Object替换
(1).对于普通方法
Pair<String> pair = PairFunctin.minMax(strings);
String first = pair.getFirst();
擦除getFirst()的返回类型后将返回Object类型,编译器会自动插入String的强制类型转换,编译器将把这个方法调用翻译为两条虚拟机指令:
1).对原始方法pair.getFirst的调用
2).将返回的Object类型强制转换为String类型
(2).对于域---假设first与second为public T...
String s = pair.first; //编译器也会自动进行强制转换,Object转换为String
9.翻译泛型方法
类型擦除也会出现在泛型方法中
public
static <T
extends Comparable> Pair<T> minmax(T[]
a){...}
原类型为:
public static Pair<Comparable> minmax(Comparable[] a){....}
实例:
public
class GenericMethodErases
extends Pair<LocalDate> { //子类去继承一个泛型类时,泛型类中的参数类型必须指明,要不然,子类必须也为泛型类---这个结果与前面的一样,这里只用普通类继承Pair进行试验
/*
* 当父类中有有参构造方法时,子类中也必须有相同的构造函数
*/
public GenericMethodErases(LocalDate
first, LocalDate
second) {
super(
first,
second);
}
/* 当父类Pair中没有无参构造方法时,在子类中有无参构造方法,会发生下面的错误:
*
Implicit super constructor Pair<LocalDate>() is undefined. Must explicitly invoke another constructor
*/
public GenericMethodErases(){}
/*
* 此时的setSecond方法可以看成是Pair类中的setSecond方法的“重载”
* (non-
Javadoc
)
* @see com.genercity.classt.Pair#setSecond(java.lang.Object)
*/
public
void setSecond(LocalDate
localDate){
if (
localDate.compareTo(getFirst()) <= 0) { //当为getFirst()时,调用的是子类中的getFirst方法,如下结果,当为super.getFirst()时,调用的是父类中的getFirst()方法
System.
out.println(
"这是子类中的setSecond方法");
super.setSecond(
localDate);
//父类Pair中的set方法
}
}
/*
* 此时的getFirst方法可以看成是Pair类中的getFirst方法的“重写”
* (non-
Javadoc
)
* @see com.genercity.classt.Pair#getFirst()
*/
@override
public LocalDate getFirst() {
System.
out.println(
"这是子类中的getFirst方法:");
return
super.getFirst();
}
}
在PairTest类中:
System.
out.println(
"********************");
LocalDate
localDate = LocalDate.
of(1992, 4, 24);
LocalDate
localDate1 = LocalDate.
of(1988, 2, 11);
GenericMethodErases
gen =
new GenericMethodErases(
localDate,
localDate1);
gen.setSecond(LocalDate.
of(1979,11,12));
System.
out.println(
gen.getFirst().toString());
System.
out.println(
"********************");
/*
* 在虚拟机中只有普通的类,没有泛型类,所以当将GenericMethodErases类对象赋给Pair类对象时,存在两个setScond方法:
* public void setScond(Object second)---父类中,public void setScond(LocalDate local)---子类中
* 这与继承中重写的条件不符,当去掉@override时,可以将其看成重载,但在《Java核心技术 卷一》中:
* 希望对setSecond的调用具有多态性,并调用最合适的那个方法,由于Pair引用了GenericMethodErases类对象,所以应该调用GenericMethodErases类中的setScond方法,
* 问题在于类型擦除与多态发生了冲突,要解决这个问题,就需要编译器在Pair类中生成一个桥方法,(原书写的是在GenericMethodErases类中)
* public void setSecond(Object second){setSecond((LocalDate)second);}------隐含强制转换
* 在该书中,将public Object getFirst()---父类中,public LocalDate getFirst()---子类中,不能这样编写Java代码(在这里,具有相同参数类型的两个方法是不合法的)
* 但在虚拟机中,用参数类型和返回类型确定一个方法。因此,编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能给正确地处理这一情况
*
*/
Pair<LocalDate>
gen1 =
new GenericMethodErases(
localDate,
localDate1);
gen1.setSecond(LocalDate.
of(1969,12,12));
System.
out.println(
gen1.getFirst().toString());
结果为:
********************
这是子类中的getFirst方法:
这是父类中的getFirst方法
这是子类中的setSecond方法
这是父类中的setSecond方法
这是子中的getFirst方法:
这是父类中的getFirst方法
1992-04-24
********************
这是子类中的getFirst方法:
这是父类中的getFirst方法
这是子类中的setSecond方法
这是父类中的setSecond方法
这是子类中的getFirst方法:
这是父类中的getFirst方法
1992-04-24
注意:
有关于Java泛型转换的事实:
1.虚拟机中没有泛型,只有普通的类和方法
2.所有的类型参数都用它们的限定类型替换
3.桥方法被合成来保持多态
4.为保持类型安全性,必要时插入强制类型转换
9.约束与局限性----大部分是由类型擦除引起
(1).不能用基本类型实例化参数类型----因为类型擦除,擦除之后,Pair类含有Object类型的域,而Object不能存储基本类型值
(2). 运行时类型查询只适用于原始类型
a instanceOf Pair<String> -----error
a instanceOf Pair<T> -----error
Pair<String> p = (Pair<String>)a; -----warning
Pair<String> p1 = new Pair<String>;
Pair<Integer> p2 = new Pair<Integer>;
System.out.println(p1.getClass == P2.getClass) ------true,因为类型擦除之后,都变成了Pair<Object>
(3). 不能创建参数化类型的数组
Pair<String>[] table = new Pair<String>[10];------error
因为类型擦除之后,数组就变成了Pair[] -----Pair[] table = new Pair[10];
Object[] o = table;
o[0] = "hello" ; //error----component type is Pair,抛出ArrayStoreException:抛出以表示尝试将错误类型的对象存储到对象数组中,如Object x[] = new String[3]; x[0] = new Integer(0);
o[1] = new Pair<String>("123","fjdjf");
System.out.println("o[0]为:" + o[0]); ---------ArrayStoreException,运行时才会出现错误
注意:不允许创建,但是能够声明类型为Pair<String>[],即不能通过new Pair<String>[10]初始化
可以声明通配类型的数组,然后进行类型的转换
Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];-----因为对于通配符的方式,最后取出数据是要做显式的类型转换的
List<?>[]
lsa =
new List<?>[10];
// OK, array of unbounded
wildcard
type.
/*
* 在这里犯了一个错误:List为接口,不能实例化,但将上面的这句,在类型擦除之后,看成了List[] list = new List[10],是new了10个List类的对象,其实这里只是创了10个空间,即一个数组,包含10个List元素
* List[] list = new List[10]; //正确的,但是会报一个警告,---
List是原始类型。
应该参数化对泛型类型列表<E>的引用,如: List<String> list = new List[10];
* 但是new List[10],会报一个经过----
类型安全:类型List[]的表达式需要未经检查的转换以符合List<String>[]
*/
Object
o =
lsa;
Object[]
oa = (Object[])
o;
List<Integer>
li =
new ArrayList<Integer>();
li.add(
new Integer(3));
oa[1] =
li;
// Correct.
Integer
i = (Integer)
lsa[1].get(0);
// OK
System.
out.println(
i);
结果为:3
注意:如果需要收集参数化类型对象,只有一种安全而有效的方法:使用ArrayList:ArrayList<Pair<String>>
(4).警告----@SuppressWarnings("unchecked") @SafeVarargs
(5).不能实例化类型变量----new T(....),new T[...],T.Class这样的表达式都是不行的
(6).不能构造泛型数组
(7).泛型类的静态上下文中类型变量无效---不能在静态域或方法中引用类型变量
(8).不能抛出或捕获泛型类的实例---即不能抛出也不能捕获泛型类对象,实际上,甚至泛型类扩展Throwable都是不合法的
public class Problem<T> extends Exception{....}---error
catch子句中不能使用类型变量----...catch(T e){Logger.global.info(...);}----error
在异常规范中使用类型变量是允许的---public static < T extends Throwable> void doWork(T t) throws T{
try{
.....
}catch(Throwable realCause){
t.initCause(realCause);
throw t;
}
}
(9).可以消除对受查异常的检查---Java异常处理的一个基本原则是,必须为所有受查异常提供一个处理器,不过可以利用泛型消除这个限制(详细的细节看《Java核心技术 卷一》)
(10).注意擦除后的冲突---(详细的细节看《Java核心技术 卷一》)
10.泛型类型的继承规则
1.Manager继承了Employee
Pair<Manager> p3 = new Pair<Manager>();
Pair<Employee> p4 = PairFunction.minMax(p3); //error---因为minMax返回的是Pair<Manager>,而不是Pair<Employee>
无论S和T是什么关系,通常,Pair<Manager>和Pair<Employee>之间没有什么关系
Pair<Manager> manager = new Pair<>("ceo","cfo");
Pair<Employee> employee = manager; //illegal but suppose it wasn't
employee.setFirst("lowEmployee"); // 不可能将一个低员工设置成为管理人员
2.泛型类可以扩展为或实现其他的泛型类---ArrayList<Manager>可以实现List<Manager>接口,而不是去实现一个List<Employee>接口
11.通配符类型
1.子类类型限定-------
可以从泛型对象读取
Pair<? extends Employee>---表示任何泛型Pair类型,它的类型参数是Employee的子类
public static void printBuddies(Pair<Employee> p){
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + "and" + second.getName() + " are buddies";
}
}
在前面提及过,不能将Pair<Manager>传递给这个方法,但现在可以使用通配符类型:
public static void printBuddies(Pair<? extends Employee p)----Pair<Manager>是Pair<? extends Employee>的子类型
Pair<Manager> managerBuddies = new Pair<>("ceo","cfo");
Pair<? extends Employee> wildcardBuddies = managerBuddies; //OK
wildcardBuddies.setFirst("lowEmployee"); //error---这是因为:Pair<? extends Employee>中,
? extends Employee getFirst()
void setFirst(? extends Employee) //编译器只知道需要每个Employee的子类型,但是不知道什么具体类型,它拒绝传递任何特定的类型,
毕竟?不能用来匹配
但是,这里的getFirst()方法可以调用
2.超类型限定-------
可以从泛型对象写入
Pair<? super Manager> ----这个通配符限制为Manager的所有超类型
在Pair<? super Manager>中:void setFirst(? super Manager) 和? super Manager getFirst():
编译器无法知道setFirst方法的具体类型,因为调用这个方法时不能接受类型为:Employee或Object的参数,只能传递Manager类型的对象,或者某个子类型对象
如果调用getFirst,不能保证返回对象的类型,只能把它赋给一个Object
public static void minmaxBonus(Manager[] a,Pair<? super Manager> result){
if(a.length == 0) return;
Manager min = a[0];
Manager max = a[0];
for(int i =0; 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.setBonus(max);
}
超类型限定的另一种应用
public static <T extends Comparable<? super T>> T min(T[] a){
......
@Override
public int compareTo(? super T){...}
}
3.无限定通配符
Pair<?>---有如下方法: ? getFirst() 和 void setFirst(?)---getFirst()的返回值只能赋给一个Object,setFirst()方法不能被调用,甚至不能被Object调用
Pair<?>与Pair类的本质不同在于:可以用任意Object对象调用原始Pair类的setObject方法
用处:用来测试Pair是否包含一个null引用
pubic static boolean hasNull(Pair<?> p){
return p.getFirst() == null || p.getSecond() == null;
通过将hasNull转换成泛型方法,可以避免使用通配符类型:public static <T> boolean hasNull(Pair<T> p)--但是,带有通配符的版本可读性更强
4.通配符捕获
public static <T> void swpHelper(Pair<T> p){
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(T);
}
public static void swap(Pair<?> p){
swpHelper(p);
}
swapHelper方法的参数T捕获通配符,它不知道是哪种类型的通配符,但是,这是一个明确的类型,并且<T>swapHelper的定义只有在T指出类型时才有明确的含义
public static void maxminBonus(Manager[] a,Pair<? super Manager> result){
minmaxBonus(a,result);
P.swap(result);
}
通配符捕获只有在有许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个的,确定的类型,如ArrayList<Pair<T>>中的T永远不能捕获ArrayList<Pair<?>>中的通配符
数组列表可以保存两个Pair<?>,分别针对?的不同类型
12.反射与泛型---详细的细节看《Java核心技术 卷一》