为什么要使用泛型?
理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:
1. List<Apple> box = ...;
2. Apple apple = box.get(
3. 0
4. );
上面的代码自身已表达的很清楚:box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。没有泛型,上面的代码需要写成这样:
1. List box = ...;
2. Apple apple = (Apple) box.get(
3. 0
4. );
很明显,泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换操作:编译器保证了这些类型转换的绝对无误。
相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检查,发现其中的错误。
谁想成为泛型程序员
泛型程序员的任务是预测出所用类的未来所有可能有的所有用途。
例如:ArrayLIst<Manager>所有元素添加到ArrayLIst<Employee>中没有问题,但是反过来就符合逻辑,如何用通配符来实现?
泛型的简单使用
见com.lmk.test.generic
package com.lmk.test.generic;
/**
* Pair<T>为类型参数。
* @author lmk
*
* @param <T>
*/
public class Pair<T> {
private T first;
private T second;
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
public Pair(T first,T second){
this.first = first;
this.second = second;
}
public Pair(){
this.first = null;
this.second = null;
}
@Override
public String toString() {
// TODO Auto-generated method stub
String result = "first="+first.toString()+"; second = "+second.toString();
return result;
}
//这里要么override public boolean equals(Object obj)
//要么使用下面的方法会报错:Name clash: The method equals(T) of type Pair<T> has the same erasure as equals(Object) of type Object but does not override it
// public boolean equals(T value) {
// // TODO Auto-generated method stub
// return super.equals(value);
// }
public static <T> Pair<T> makePair(Class<T> cl){
try {
return new Pair<T>(cl.newInstance(),cl.newInstance());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
public static void swap(Pair<?> p){
// ? temp = p.getFirst();//? 不是类型,此句错误!
// p.setFirst(p.getSecond());
// p.setSecond(temp);
//可以使用通配符捕获 swapHelper(Pair<T> p)
swapHelper(p);
}
private static <T> void swapHelper(Pair<T> p) {
// TODO Auto-generated method stub
T temp = p.getFirst();//? 不是类型,此句错误!
p.setFirst(p.getSecond());
p.setSecond(temp);
}
}
package com.lmk.test.generic;
import java.lang.reflect.Array;
public class ArrayAlg {
/**
* <T extends Comparable<T>> 类型参数的限定
* 即传入了类型变量必须是实现了Comparable<T>的类
* @param a
* @return
*/
public static <T extends Comparable<T>> Pair<T> minmax(T[] a){
if ((a== null)||(a.length==0)) {return null;}
T min = a[0];
T max = a[0];
for (T t : a) {
if (t.compareTo(min)<0) {min =t;}
if (t.compareTo(max)>0) {max = t;}
}
return new Pair<T>(min, max);
}
//如果minmax返回不是一个Pair 而是T[2]的数组 怎么写??? 注意:不能写new T【2】
// public static <T extends Comparable<T>> T[] minmax(T[] a){
// T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), 2);
// return result;
// }
//
public static <T> T getMiddle(T[] a){
return a[a.length/2];
}
public static boolean hasNulls(Pair<?> p){//无限定通配符 ?的使用,避免了类型变量T的使用 如下 public static <T> boolean hasNulls(Pair<T> p)
return p.getFirst()== null ||p.getSecond() == null;
}
// public static <T> boolean hasNulls(Pair<T> p){
// return p.getFirst()== null ||p.getSecond() == null;
// }
}
package com.lmk.test.generic;
import java.util.Date;
import java.util.logging.Logger;
public class PairTest {
// private Logger logger1 = null;
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Pair mm = null;
Logger logger1 = Logger.getLogger(PairTest.class.getName());
String[] words = {"lmk","ld","klj","ps"};
mm = ArrayAlg.minmax(words);
logger1.info(mm.toString());
Integer[] ints ={3,4,2,7,1};
mm = ArrayAlg.minmax(ints);
logger1.info(mm.toString());
logger1.info(ArrayAlg.getMiddle(words));
//-------------------------------运行时类型查询只适用于原始类型--------------------------------------
// Boolean flag = mm instanceof Pair<String>;
//在这里直接报错 Cannot perform instanceof check against parameterized type Pair<String>.
// Use the form Pair<?> instead since further generic type information will be erased at runtime
// logger1.info((mm instanceof Pair<Integer>)+"");
logger1.info((mm instanceof Pair)+"");
logger1.info((mm instanceof Pair<?>)+"");
// logger1.info((mm instanceof Pair<T>)+"");//报错
Pair<String> p =mm;
// logger1.info(p.getFirst());//编译的时候不会报错,运行时报错
//Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
//at com.lmk.test.generic.PairTest.main(PairTest.java:35)
logger1.info(p.getClass().getName());//结果 :com.lmk.test.generic.Pair而不是com.lmk.test.generic.Pair<String>
//-------------------------------------------------------------------------
// Pair<String>[] table = new Pair<String>[10];//运行时类型查询只适用于原始类型s
//-------------------------不能实例化类型变量-----------------------------
//New T(); T.class//error
//因为String.class 就是一个Class<String>的对象实例
//--------------------------------------------------------
Date date1 = new Date();
Date date2 = new Date();
Pair<Date> interval = new DateInterval<Date>(date1,date2);
interval.setFirst(date2);//此处DateInterval的setFirst()的方法
logger1.info(interval.getClass().getName());
}
}
package com.lmk.test.generic;
public class DateInterval<T> extends Pair<T>{
@Override
public T getFirst() {
// TODO Auto-generated method stub
return super.getFirst();
}
public void setFirst(T first) {
// TODO Auto-generated method stub
super.setFirst(first);
}
public DateInterval(T first, T second) {
super(first, second);
// TODO Auto-generated constructor stub
}
}
类型变量的限定
* <T extends Comparable<T>&serializablehy>类型参数的限定
* 即传入了类型变量必须是实现了Comparable<T>的类
泛型代码和虚拟机
Ø 虚拟机没有泛型类型对象—所有对象都属于普通类。
Ø 无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。(raw type)
Ø 对有限定的类型变量,原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Object替换。
Ø 在翻译泛型表达式时,需要插入强制类型转换。
Pair<Employee> buddies=…; Employee buddy =buddies.getFirst();//檫除之后,buddies.getFirst()获得Object对象,此处必须进行强制类型转换。
Employee buddy = buddies.first; 必须进行强制类型转换
Ø 翻译泛型方法,会带来问题
类型檫除会让多态冲突,要解决这个问题,这个时候编译器会生成桥方法。
约束和局限性
不能用基本类型实例化类型参数
运行时类型查询只适用于原始类型
1. // Boolean flag = mm instanceofPair<String>;
//在这里直接报错 Cannot performinstanceofcheck against parameterized type Pair<String>.
// Use the form Pair<?> instead since furthergeneric type information will be erased at runtime
// logger1.info((mminstanceof Pair<Integer>)+"");
logger1.info((mminstanceofPair)+"");
logger1.info((mminstanceofPair<?>)+"");
logger1.info((mm instanceof Pair<T>)+"");//报错
2. 强制类型转换
Pair<String> p =mm;
logger1.info(p.getFirst());//编译的时候不会报错,运行时报错
//Exception in thread "main" java.lang.ClassCastException:java.lang.Integer cannot be cast to java.lang.String
//at com.lmk.test.generic.PairTest.main(PairTest.java:35)
3. logger1.info(p.getClass().getName());//结果 :com.lmk.test.generic.Pair而不是com.lmk.test.generic.Pair<String>
不能抛出也不能捕获泛型类实例
但是异常声明中可以使用类型变量。
Public static <T extends Throwable> void doWork(T t) throw T//OK
参数化类型数组不合法
Pair<String>[] table = new Pair<String>[10];//运行时类型查询只适用于原始类型
如果需要收集参数化类型的对象,请使用 ArrayList<Pair<String>>
不能实例化类型变量
Ø New T();因为檫除之后为 new Object();
Ø T.class//error
因为String.class 就是一个Class<String>的对象实例
那么怎么办呢?
public static <T> Pair<T>makePair(Class<T> cl){
try {
returnnewPair<T>(cl.newInstance(),cl.newInstance());
}catch(Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
returnnull;
}
}
Ø New T[]也是一件困难的事情。
public static <T extends Comparable<T>>T[]minmax(T[] a){
T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(),2);
return result;
}
泛型类的静态上下文中类型变量无效
无论是静态变量还是静态方法
注意擦除后的冲突
Ø 继承方法的命名冲突:例如Pair类中 public boolean equals(T value) 的冲突
这里的冲突和多态桥方法是有区别的
Ø 想要支持擦除的转换,就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。
如:
Class Calendarimplements Comparable<Calendar>{…..}
Class GregorianCalendarextends Calendar implements Comparable <GregorianCalendar>{…..}
泛型类型的继承规则
无论S和T有什么联系,Pair<S>Pair<T>没有什么联系
P539
对比ArrayLIst<Employee> ArrayList<Manager>与Employee[]Manager[]
永远可以将参数类型转换为原始类型
通配符类型
? extends
让我们重新看看这第二部分使用的一个例子,其中谈到了Java数组的子类型相关性:
1. Apple[] apples =
2. new
3. Apple[
4. 1
5. ];
6. Fruit[] fruits = apples;
7.
8. fruits[
9. 0
10.] =
11.new
12. Strawberry();
13.
就像我们看到的,当你往一个声明为Fruit数组的Apple对象数组里加入Strawberry对象后,代码可以编译,但在运行时抛出异常。
现在我们可以使用通配符把相关的代码转换成泛型:因为Apple是Fruit的一个子类,我们使用? extends 通配符,这样就能将一个List<Apple>对象的定义赋到一个List<? extends Fruit>的声明上:
1. List<Apple> apples =
2. new
3. ArrayList<Apple>();
4.
5.
6. List<?
7. extends
8. Fruit> fruits = apples;
9.
10.fruits.add(
11.new
12. Strawberry());
13.
这次,代码就编译不过去了!Java编译器会阻止你往一个Fruit list里加入strawberry。在编译时我们就能检测到错误,在运行时就不需要进行检查来确保往列表里加入不兼容的类型了。即使你往list里加入Fruit对象也不行:
1. fruits.add(
2. new
3. Fruit());
4.
5.
你没有办法做到这些。事实上你不能够往一个使用了? extends的数据结构里写入任何的值。
原因非常的简单,你可以这样想:这个? extends T通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。另一方面,因为我们知道,不论它是什么类型,它总是类型T的子类型,当我们在读取数据时,能确保得到的数据是一个T类型的实例:
1. Fruit get = fruits.get(
2. 0
3. );
4.
5.
? super
使用 ? super通配符一般是什么情况?让我们先看看这个:
1. List<Fruit> fruits =
2. new
3. ArrayList<Fruit>();
4.
5. List<?
6. super
7. Apple> = fruits;
8.
我们看到fruits指向的是一个装有Apple的某种超类(supertype)的List。同样的,我们不知道究竟是什么超类,但我们知道 Apple和任何Apple的子类都跟它的类型兼容。既然这个未知的类型即是Apple,也是GreenApple的超类,我们就可以写入:
1. fruits.add(
2. new
3. Apple());
4.
5. fruits.add(
6. new
7. GreenApple());
8.
如果我们想往里面加入Apple的超类,编译器就会警告你:
1. fruits.add(
2. new
3. Fruit());
4.
5.
6. fruits.add(
7. new
8. Object());
9.
因为我们不知道它是怎样的超类,所有这样的实例就不允许加入。
从这种形式的类型里获取数据又是怎么样的呢?结果表明,你只能取出Object实例:因为我们不知道超类究竟是什么,编译器唯一能保证的只是它是个Object,因为Object是任何Java类型的超类。
存取原则和PECS法则
总结 ? extends和 the ? super通配符的特征,我们可以得出以下结论:
◆如果你想从一个数据类型里获取数据,使用 ? extends通配符
◆如果你想把对象写入一个数据结构里,使用 ? super通配符
◆如果你既想存,又想取,那就别用通配符。
这就是Maurice Naftalin在他的《Java Genericsand Collections》这本书中所说的存取原则,以及Joshua Bloch在他的《Effective Java》这本书中所说的PECS法则。
Bloch提醒说,这PECS是指”ProducerExtends, Consumer Super”,这个更容易记忆和运用。
无限定通配符 ?
注意通配符不是类型变量,所有? t = p.getFirst()错误
泛型中的?通配符
如果定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,如果这样写
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.List;
- publicclass GernericTest {
- publicstaticvoid main(String[] args) throws Exception{
- List<Integer> listInteger =new ArrayList<Integer>();
- List<String> listString =new ArrayList<String>();
- printCollection(listInteger);
- printCollection(listString);
- }
- publicstaticvoid printCollection(Collection<Object> collection){
- for(Object obj:collection){
- System.out.println(obj);
- }
- }
- }
语句printCollection(listInteger);报错
The method printCollection(Collection<Object>) in the type GernericTest is not applicable for the arguments (List<Integer>)
这是因为泛型的参数是不考虑继承关系就直接报错。
这就得用?通配符
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.List;
- publicclass GernericTest {
- publicstaticvoid main(String[] args) throws Exception{
- List<Integer> listInteger =new ArrayList<Integer>();
- List<String> listString =new ArrayList<String>();
- printCollection(listInteger);
- printCollection(listString);
- }
- publicstaticvoid printCollection(Collection<?> collection){
- for(Object obj:collection){
- System.out.println(obj);
- }
- }
- }
在方法public static void printCollection(Collection<?> collection){}中不能出现与参数类型有关的方法比如collection.add();因为程序调用这个方法的时候传入的参数不知道是什么类型的,但是可以调用与参数类型无关的方法比如collection.size();
总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量的主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
几个名词
参数化类型Pair<String> 原始类型 Pair 泛型类型(泛型类)Pair<T>
类型变量 T