Java核心技术·卷一·第八章笔记

第八章 泛型程序设计

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 类型字面量

给我又看蒙蔽了,先放这儿

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值