第八章 泛型程序设计
8.1 为什么要使用泛型程序设计
8.1.1 使用泛型程序设计的好处
可以实现使用时对元素的声明,而不是使用一个Object类,这样是没有类型检查而且需要强制类型转换的
8.1.2 谁想成为泛型程序员
泛型的类型是很多的,要确保所有的类型都能正常工作不是一件容易的事情,可以通过通配符解决
8.2 定义简单泛型类
有一个或者多个类型变量的类
用E来表示集合类型的元素类型,用K,V来表示键值和键的类型,T(U,S)表示任意类型
package com.package1;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Test {
public static void main(String[] args) {
String[] a = {"wuhu","qifei","njq","java","100"};
Pair<String> p = ArrayLg.minmax(a);
System.out.println(p.getFirst());
System.out.println(p.getSecond());
}
}
class Pair<T>{
private T first;
private T second;
Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst(){
return first;
}
public T getSecond(){
return second;
}
}
class ArrayLg{
public static Pair<String> minmax(String[] a){
if(a.length==0 || a==null){
return null;
}
String max = a[0];
String min = a[0];
for(int i = 0; i < a.length; i++){
if(max.compareTo(a[i]) < 0){
max = a[i];
}
if(min.compareTo(a[i]) > 0){
min = a[i];
}
}
return new Pair<>(min,max);
}
}
8.3 泛型方法
泛型方法可以定义在泛型类中,也可以定义在普通类中。
class ArrayLg{
public static <T> T getMiddle(T... a){
return a[a.length/2];
}
}
在调用时可以将具体类型放在方法名后面,但是一般编译器可以识别,多省略
public static void main(String[] args) {
String[] a = {"wuhu","qifei","njq","java","100"};
ArrayLg.getMiddle(a);
ArrayLg.<String>getMiddle(a);
}
而在C++中,强制的要求需要把具体类型放在方法名后面,这有可能会导致歧义
g( f<a,b>(c) );
//是先调用f方法,还是两个逻辑表达?
8.4 类型变量的限定
extends限定了这个类型需要是某个类或者接口的子类
public static <T extends Comparable> T minmax(T... a){
...code
}
//用&并列
其中如果有类,类必须在第一位
package com.package1;
public class Test {
public static void main(String[] args) {
String[] a = {"wuhu","qifei","njq","java"};
Pair<String> p = ArrayLg.minmax(a);
System.out.println(p.getFirst());
System.out.println(p.getSecond());
}
}
class Pair<T>{
private T first;
private T second;
Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst(){
return first;
}
public T getSecond(){
return second;
}
}
class ArrayLg{
public static <T extends Comparable> Pair<T> minmax(T[] a){
if(a.length==0 || a==null){
return null;
}
T max = a[0];
T min = a[0];
for(int i = 0; i < a.length; i++){
if(max.compareTo(a[i]) < 0){
max = a[i];
}
if(min.compareTo(a[i]) > 0){
min = a[i];
}
}
return new Pair<>(min,max);
}
}
8.5 泛型代码和虚拟机
8.5.1 代码擦除
会提供一个原始类型去代替泛型类型,如果没有任何限定,就是Object
有不同的类型的时候,会替换成第一个extends的类型
class Erase <T extends Comparable & Serializable> implements Serializable{
private T a;
}
class Erase implements Serializable{
private Comparable a;
}
class Erase <T extends Serializable & Comparable> implements Serializable{
private T a;
}
class Erase implements Serializable{
private Serializable a;
//这样可能会涉及一些强制类型转换
//所以要尽可能的将标志性接口放在后面
}
8.5.2 转换泛型表达式
如上文所言,由于代码擦除的原因,在进行调用赋值的时候会自动进行强制类型转换
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
//此方法调用时隐式进行了强制类型转换
//对公共字段,在赋值时当然也存在这种情况
8.5.3 转换泛型方法
也会进行类型擦除,其中使用桥方法来实现多态。
package com.package1;
public class Test {
public static void main(String[] args) {
Pair<Integer> pair =new A();
pair.setData(1);
//result:Integer setData
}
}
class A extends Pair<Integer>{
@Override
public void setData(Integer integer){
System.out.println("Integer setData");
}
}
class Pair<T>{
private T data;
public void setData(T data){
System.out.println("Pair setData");
}
}
类型擦除之后变成了:
class Pair{
private Object data;
public void setData(Object data){
System.out.println("Pair setData");
}
}
class A extends Pair{
@Override
public void setData(Integer integer){//如果这样来看,子类A是没有覆写超类方法的
System.out.println("Integer setData");
}
}
编译器已经知道了pair引用了一个A对象,就去调用A对象的setData方法,可是类型擦除与多态发生了冲突,就在子类中生成了一个桥方法来解决这个问题
public void setData(Object data){
setData((Integer) data);
}
//这个方法覆写了超类方法,充当一个桥的作用
对于get方法,可想而知会有两个签名类型相同的方法,这在编写代码的时候是不允许的,但是对虚拟机来说,返回类型不同,是可以分辨的。
本质上,协变的方法也是通过桥方法来实现的
class A{
@Override
protected A clone() throws CloneNotSupportedException {
return new A();
}
//覆写的返回类型更严了,称之为有协变的返回类型
//实际上又是通过一个桥方法来实现调用的,该桥方法完全与clone相同,但是实现是去调用程序员协变的方法
}
8.5.4 调用遗留代码
在Java5之前是没有泛型类的,对于一些遗留的代码,与泛型类之间进行转换可能会造成潜在的强制类型转化错误,但是在确保不会发生错误的情况下,可以使用@SuppressWarnings(“unchecked”)忽略
8.6 限制与局限性
8.6.1 不能用基本类型实例化类型参数
例如没有Pair,但是可以使用包装类解决,无伤大雅
8.6.2 运行时类型查询只适用于原始类型
在运行的时候,查询类型,无论类型参数是什么,全都只会查出来原始类型。
package com.package1;
public class Test {
public static void main(String[] args) {
A a = new A();
System.out.println(a instanceof Pair<String>);//报错,不论填任何类型都是等同于下一句
System.out.println(a instanceof Pair);//通过
System.out.println(p.getClass());//class com.package1.Pair
}
}
class A extends Pair<String>{
}
class Pair<T>{
private T a;
}
8.6.3 不能创建参数化类型的数组
不能实例化参数化类型的数组
package com.package1;
public class Test {
public static void main(String[] args) {
var table = new Pair<String>[10];
}
}
class Pair<T>{
private T a;
}
//报错:
//C:\Users\MuRuo\Desktop\Dev\IDEA\JavaRelearn\src\com\package1\Test.java:5:21
//java: 创建泛型数组
//如果成功创建,有
Object objarray = table;
objarray[0] = "hello";//ArrayStoreException
//但是对于泛型实例化,这样的异常检测是无效的
objarray[0] = new Pair<Integer>();
//那么就可能产生类型错误
但是,可以声明参数化类型的数组
var a = (Pair<String>[]) new Pair<?>[10];
//声明通配类型的数组,再强制类型转换,但是这是存在ClassCastException异常的风险的
8.6.4 Varargs警告
varargs即可变参数,例如
public static <T> void addAll(Collection<T> coll,T... ts){
for(T t : ts){
coll.add(t);
}
}
对于以下调用:
Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Paor<String> pair2 = ...;
addAll(table,pair1,pair2);
JVM会建立一个泛型数组,但是只是一个警告,可以使用@SafeVarargs忽略。
可以使用@SafeVarargs加可变参数方法来消除对泛型数组创建的有关限制,但是这是不安全的.
@SafeVarargs
static <E> E[] array(E... array){
return array;
}
//可以调用:
Pair<String>[] table = array(pair1,pair2);
//但是基于代码擦除,就有可能出现异常,除非对于数组的元素类型十分清晰
8.6.5 不能实例化类型变量
调用T()是不合法的。(类型擦除变成了Object())
在Java8之后的做法是传入一个构造表达式
public static <T> Pair<T> makePair(Supplier<T> constr){
return new Pair<>(constr.get());
}
或者使用反射,但是T.class是不合法的,需要设计API
public static <T> Pair<T> makePair(class<T> cl){
try{
return new Pair<>(cl.getConstructor().newInstance());
}catch(Exception e){
return null;
}
}
实际上Class类本身就是泛型的
8.6.6 不能创造泛型数组
new T[]是不合法的。
例如返回一个T[]数组,在运行过程中就可能造成ClassCastExcption。
public static <T extends Comparable> T[] minmax(T...){
var result = new Comparable[2];
...;
return (T[]) result;//由于类型擦除的缘故,这个强制类型转换是没有任何意义的
}
//对于以下调用
String[] result = minmax("wuhu","qifei");
//就会产生ClassCastException
//较好的解决方法是传入一个构造器
String[] result = minmax(String[]::new,"wuhu","qifei");
public static <T extends Comparable> T[] minmax(INtFunction<T[]> constr, T...){
T[] result = const.apply(2);
...
}
//或者说可以使用反射
public static <T extends Comparable> T[] minmax(class<T> cl, T...){
var result = (T[])Array.newInstance(cl.getclass().getComponentType(),2);
...
}
8.6.7 泛型类的静态上下文中类型变量无效
不能声明泛型类型的static字段或者方法,这也是显而易见的,static类型的共用机制使得类型可能会出现错误,以及static方法可以通过类来调用使得类型是绝对不确定的。
8.6.8 不能抛出或捕捉泛型类的实例
实际上,泛型类扩展Throwable都是不合法的
class Problem<T> extends Exception{
//error;
}
catch语句中不能使用类型变量
public static <T extends Throwable> void doWork(Class<T>){
try{
do work;
}catch(T t){
Logger.global.info(...);
}//error
}
但是在异常规范中使用类型变量是允许的
public static <T extends Throwable> void doWork(){
try{
do work;
}catch(Throwable realCause){
t.initCause(realCause);
throw t;
}
所以准确来讲是不能捕捉类型类的对象,但是可以抛出init之后的捕捉类的对象
}
8.6.9 可以取消对检查型异常的检查
package com.package1;
public class Test {
public static void main(String[] args) {
var thread = new Thread(Task.asRunnable(
()->{
Thread.sleep(1000);
System.out.println("Hello world!");
throw new Exception("Check this out");
}
));
thread.start();
}
}
interface Task{
void run() throws Exception;
@SuppressWarnings("unchecked")
static <T extends Throwable> void throwAs(Throwable t) throws T{
throw (T) t;
}
static Runnable asRunnable(Task task){
return () ->{
try{
task.run();
}catch (Exception e){
Task.<RuntimeException>throwAs(e);
}
};
}
}
还是不太懂,见书P344
8.6.10 注意擦除后的冲突
例如对于
public class Pair<T>{
public boolean equals(T value){
return first.equals(value)&&second.equals(value);
}
}
//类型擦除之后
public boolean equals(Object value){
...
}
//这不就和Object类的equals方法发生冲突了吗,而且一个类不能作为同一个接口的不同参数化的子类
class Employee implemets Comparable<Employee>{}
class Manger extends Employee implements Comparable<Manger>{}
//类型擦除之后,这是有问题的,主要就在于桥函数
//对于Comparable<T>会生成一个桥函数
public int ComparaTo(Object other){
return ComparaTo((T) other);
}
8.7 泛型类型的继承规则
无论T与S是任何关系,Pair与Pair没有任何关系.
但是例如ArrayList是extends List的,那么ArrayList就是List的子类
8.8 通配符类型
package com.package1;
public class Test {
public static void main(String[] args) {
Pair<Manager> p = new Pair<>();
Utils.print(p);//这是会报错的,不能Pair<Employee>与Pair<Manager>是没有任何关系的,会发生强制类型转换异常.
}
}
class Employee{
}
class Manager extends Employee{
}
class Utils{
public static void print(Pair<Employee> p){
}
}
class Pair<T>{
public T first;
public T second;
}
/
class Utils{
public static void print(Pair<? extends Employee> p){
}
}
//但是改成通配符类型就不会报错了
Pair<? extends Employee>是Pair<Manager>的超类。
而且这是安全的,例如
package com.package1;
public class Test {
public static void main(String[] args) {
Pair<Manager> p = new Pair<>();
Pair<? extends Employee> p2 = p;
p2.setFirst(new Employee());//这是会报错的
//因为对于setFirst,它变成了:
public void setFirst(? extends Employee){
...
}
//但是JVM只知道?是Emplyee的一个子类,具体是什么并不知道,所以直接报错不执行
//对于getter不存在。就直接返回就行了。
}
}
class Employee{
}
class Manager extends Employee{
}
class Pair<T>{
private T first;
private T second;
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
}
8.8.2 通配符的超类型限定
想比较于类型变量限定,通配符还可以指定超类型限定
? super Manager
与extends限定相比,超类型限定可以使用setter但是无法使用getter
public static <T extends Comparable<? super T>> name(...){
//这样,所有使用Comparable的对象都可用。而且getter和setter都可用
}
8.8.3 无限定通配符
Pair<?>与Pair的根本不同在于,可以使用任意的Object来调用Pair,但是对于Pair<?>getter只能赋给Object,setter不可用
使用这个脆弱的类型,大部分是为了增强可读性
public static void hasNulls(Pair<?>){
return p.getFirst() == null || p.getSecond() == null;
}
public static <T> boolean hasNulls(Pair<>){
return ...
}
8.8.4 通配符捕获
对于
public static void swap(Pair<?> p)
?不是一种类型,以下是不合法的
? temp = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
对于需要知道具体通配符类型,可以通过通配符捕捉的一个helper来实现
public static <T> void swapHelper(Pair<T> p){
....
}
public static void swap(Pair<?> p){
swapHelper(p);
}
通配符机制是不可避免的.
下面是个较为全面的例子
package com.package1;
public class Test {
public static void main(String[] args) {
var ceo = new Manager("ceo");
var cfo = new Manager("cfo");
var buddies = new Pair<Manager>(ceo,cfo);
printBuddies(buddies);
ceo.setBonus(10000);
cfo.setBonus(20000);
Manager[] managers = {ceo,cfo};
var result = new Pair<Employee>();
minmaxBonus(managers,result);
System.out.println(result.getFirst().getName()+" "+result.getSecond().getName());
maxminBonus(managers,result);
System.out.println(result.getFirst().getName()+" "+result.getSecond().getName());
}
public static void printBuddies(Pair<? extends Employee> p){
System.out.println(p.getFirst().getName()+" "+p.getSecond().getName());
}
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(a[i].getBonus() > max.getBonus()){
max = a[i];
}
if(a[i].getBonus() < min.getBonus()){
min = a[i];
}
}
result.setFirst(min);
result.setSecond(max);
}
public static void maxminBonus(Manager[] a,Pair<? super Manager> result){
minmaxBonus(a,result);
PairAlg.swap(result);
}
}
class Employee{
private String name;
public Employee(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Manager extends Employee{
private double bonus;
public Manager(String name) {
super(name);
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
class Pair<T>{
private T first;
private T second;
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public Pair() {
}
}
class PairAlg{
public static boolean hasNulls(Pair<?> p){
return p.getFirst() == null || p.getSecond() == null;
}
public static void swap(Pair<?> p){
swapHelper(p);
}
private static <T> void swapHelper(Pair<T> p){
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
}
result:
ceo cfo
ceo cfo
cfo ceo
8.9 反射与泛型
由于类型擦除的原因,对泛型使用反射将不会得到太多信息
8.9.1 泛型class类
class类是泛型的,例如String.class实际上是一个class对象
类型参数可以使得返回时返回对应的类型,从而避免了使用时的强制类型转换。
8.9.2 使用Class参数进行类型匹配
package com.package1;
public class Test {
public static void main(String[] args) {
}
public static <T> Pair<T> makePair(Class<T> tClass) throws InstantiationException, IllegalAccessException {
return new Pair<>(tClass.newInstance(),tClass.newInstance());
}
}
class Pair<T>{
private T first;
private T second;
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
}
8.9.3 虚拟机在的泛型类型信息
为了表述泛型类型声明,可以使用java.lang.reflect包中的接口Type,它有如下子类及其功能
- Class类,描述具体类型
- TypeVariable接口,描述类型变量(如 T extends Comparable<? super T>)
- WildcardType接口,描述通配符(如 ? super T)
- ParameterizedType接口,描述泛型类或接口类型(eg: Comparable<? super T>)
- GenericArrayType接口,描述泛型数组(T[])
package com.package1;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Scanner;
public class Test {
public static void main(String[] args){
String name;
if(args.length > 0){
name = args[0];
}
else{
try(var in = new Scanner(System.in)){//这种语法是错误处理那里的
System.out.println("Please input name");
name = in.next();
}
}
try{
Class<?> cl = Class.forName(name);
printClass(cl);
for(Method m: cl.getMethods()){
printMethod(m);
}
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
private static void printClass(Class<?> cl){
System.out.print(cl);
printTypes(cl.getTypeParameters(),"<",",",">",true);
Type sc = cl.getGenericSuperclass();
if(sc != null){
System.out.print(" extends ");
printType(sc,false);
}
printTypes(cl.getGenericInterfaces()," implements ", "," , "", false);
System.out.println();
}
private static void printMethod(Method m){
String name = m.getName();
System.out.print(Modifier.toString(m.getModifiers()));
System.out.print(" ");
printTypes(m.getTypeParameters(),"<", ", ", ">", true);
printType(m.getGenericReturnType(),false);
System.out.print(" ");
System.out.print(name);
System.out.print("(");
printTypes(m.getGenericParameterTypes(), "", ", ", "", false);
System.out.println(")");
}
private static void printTypes(Type[] types, String pre, String sep, String suf, boolean isDefinition){
if(" extends ".equals(pre) && Arrays.equals(types, new Type[]{Object.class}))
return;
if(types.length > 0){
System.out.print(pre);
}
for(int i = 0; i < types.length ; i++){
if(i > 0){
System.out.print(sep);
}
printType(types[i],isDefinition);
}
if(types.length > 0){
System.out.print(suf);
}
}
private static void printType(Type type, boolean isDefinition){
if(type instanceof Class){
var t = (Class<?>) type;
System.out.print(t.getName());
}
else if(type instanceof TypeVariable){
var t = (TypeVariable<?>) type;
System.out.print(t.getName());
if(isDefinition)
printTypes(t.getBounds()," extends "," & ","",false);
}
else if(type instanceof WildcardType){
var t = (WildcardType) type;
System.out.print("?");
printTypes(t.getUpperBounds()," extends ", " & ", "" ,false);
printTypes(t.getLowerBounds(), " super ", " & ", "",false);
}
else if(type instanceof ParameterizedType){
var t = (ParameterizedType) type;
Type owner = t.getOwnerType();
if(owner != null){
printType(owner,false);
System.out.print(".");
}
printType(t.getRawType(),false);
printTypes(t.getActualTypeArguments(),"<",", ",">",false);
}
else if(type instanceof GenericArrayType){
var t = (GenericArrayType) type;
System.out.print("");
printType(t.getGenericComponentType(),isDefinition);
System.out.print("[]");
}
}
}
class Pair <T extends Comparable> {
public <T extends Comparable<? super Comparable>>
void f(){}
}
给我看蒙蔽了,先放这儿
8.9.4 类型字面量
给我又看蒙蔽了,先放这儿