Java高级程序设计

泛型

泛型

泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。... Ada、Delphi、Eiffel、Java、C#、F#、Swift 和 Visual Basic .NET 称之为泛型(generics);ML、Scala 和 Haskell 称之为参数多态(parametric polymorphism);C++ 和 D称之为模板。《Design Patterns》一书称之为参数化类型(parameterized type)。

https://zh.wikipedia.org/wiki/泛型

Java Generics

  • A way to control a class type definitions
  • A way of improving the clarity of code
  • A way of avoiding (casts) in code, turning run-time errors (typically ClassCastException) into compile-time errors.

葫芦娃中的例子

Position第一版

public class Position {
    private Object sth;
    public Position(Object sth) { 
        this.sth = sth; 
    }
    Object get() { 
        return sth; 
    }
}
Postion里放任意东西

改进

Position第二版

public class Position {
    private Creature sth;
    public Position(Creature sth) { 
        this.sth = sth; 
    }
    Creature get() { 
        return sth; 
    }
}

通过构造方法和get方法我们能看出Position里放的是Creature

但如果是这样呢?

public class Position {
    private Creature sth;
}

从外部观察,何以了解Position是放Creature的?

用“泛型”显式说明一下

public class Position<T>{
    private T sth;
}

显式表达“某种不确定”: Position里是要放东西的,但不确定是什么东西

再明白一点

public class Position<T extends Creature>{
    private T sth;
}
显式表达Position是跟某种Creature有关系的

不用泛型的Stack

class Stack{
  void push(Object o){...}
  Object pop(){...}
}

String s = "Hello";
Stack st = new Stack();
...
st.push(s);
...
s = (String)st.pop();

用了泛型的Stack

class Stack<A>{ // 类参数
  void push(A a){...}
  A pop(){...}
}
String s = "Hello";
Stack<String> st = new Stack<String>();
st.push(s);
...
s = st.pop();

再定义一个Computer 

public class Computer{

    private HDD mHarddisk;   // 机械硬盘
    
    Computer(HDD harddisk){
        mHarddisk = harddisk;
    }
    public Data readDisk(){
        return mHarddisk.read();
    }
    public void writeDisk(Data data){
        mHarddisk.write(data);
    }
}
如果安装的是SSD怎么办?

定义一个SSDComputer ?

public class SSDComputer{

    private SSD mHarddisk;   // SSD硬盘

    Computer(SSD harddisk){
        mHarddisk = harddisk;
    }
    public Data readDisk(){
        return mHarddisk.read();
    }
    public void writeDisk(Data data){
        mHarddisk.write(data);
    }
}
这当然不好!

抽象一下

public abstract class Disk{};
public class SSD extends Disk{};
public class HHD extends Disk{};
public class Computer{
    private Disk disk;   // 抽象的硬盘
    Computer(Disk disk){
        this.disk = disk;
    }
    public Data readDisk(){ return disk.read(); }
    public void writeDisk(Data data){ disk.write(data); }
    public Disk getDisk(){ return disk; }
}
基于多态实现设计抽象(解耦)
但你还要cast

用泛型来做

public class Computer<T extends Disk>{
    private T disk;   // 参数类
    Computer(T disk){ this.disk = disk; }
    public Data readDisk(){ return disk.read(); }
    public void writeDisk(Data data){ disk.write(data); }
    public T getDisk(){ return disk; }
    public void setDisk(T disk){ this.disk = disk; }

    public static void main(String[] args) {
        Computer<SSD> computer = new Computer<SSD>(new SSD());
        SSD disk = computer.getDisk(); // No cast needed
        //computer.setDisk(new HHD()); // error!
    }
}
有了类型约束

泛型方法

public class GenericMethods {
    public <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }
    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1); //autoboxing
        gm.f(1.0);
        gm.f(1.0F);
        gm.f(‘c’);
        gm.f(gm);
    }
}

再看容器

public class Holder<T> {
    private T obj;
    public void set(T obj){ this.obj = obj; }
    public T get(){ return obj; }
    public static void main(String[] args){
        Holder<Integer> holder = new Holder<>();
        holder.set(1);
        //holder.set("Abc"); // error
        Integer obj = holder.get(); //无须cast
    }       
}
多完美!可惜这只是编译时刻... 因为运行时的类型信息被擦掉了
javap -v -p -s -sysinfo -constants Holder.class

字节码中

Constant pool:
    #1 = Methodref          #9.#29         // java/lang/Object."<init>":()V
    #2 = Fieldref           #3.#30         // Holder.obj:Ljava/lang/Object;
    #3 = Class              #31            // Holder
    #4 = Methodref          #3.#29         // Holder."<init>":()V
    #5 = Methodref          #8.#32         // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
    #6 = Methodref          #3.#33         // Holder.set:(Ljava/lang/Object;)V
    #7 = Methodref          #3.#34         // Holder.get:()Ljava/lang/Object;
    #8 = Class              #35            // java/lang/Integer
    #9 = Class              #36            // java/lang/Object
    ...
    #29 = NameAndType        #14:#15        // "<init>":()V
    #30 = NameAndType        #10:#11        // obj:Ljava/lang/Object;
    #31 = Utf8               Holder
    #32 = NameAndType        #37:#38        // valueOf:(I)Ljava/lang/Integer;
    #33 = NameAndType        #18:#19        // set:(Ljava/lang/Object;)V
    #34 = NameAndType        #21:#22        // get:()Ljava/lang/Object;

擦除

public class Computer<T extends Disk> {
    private T disk; // 运行时disk是Disk类型
    Computer(T disk) {
        disk = disk;
    }
    public void setDisk(T disk) {
        this.disk = disk;
    }
}
class Disk {};

Java泛型的实现方式是将类型参数用边界类型替换,在上面的例子中就是把T用Disk替换。这种实现方式看上去就像是把具体的类型擦除到了边界类型(父类Disk)。

字节码

Constant pool:
    #1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
    #2 = Fieldref           #3.#19         // Computer.disk:LDisk;
    #3 = Class              #20            // Computer
    #14 = Utf8              setDisk
    #19 = NameAndType       #5:#6          // disk:LDisk;
    
    public void setDisk(T);
        descriptor: (LDisk;)V
        flags: (0x0001) ACC_PUBLIC
        Code:
        ...

这两个是不同类型么?

https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html

ArrayList<Integer> intList = new ArrayList<>();
ArrayList rawList = new ArrayList();

System.out.println(intList.getClass().getCanonicalName());
System.out.println(rawList.getClass().getCanonicalName());

Output:

java.util.ArrayList
java.util.ArrayList

擦除的后果

ppublic class Holder<T> {
    private T obj;
    public void set(T obj){ this.obj = obj; }
    public T get(){ return obj; }
    public void testT(Object arg){
        if (arg instanceof T){ ... } //编译错误
        T var = new T(); //编译错误
        T[] array = new T[100]; //编译错误
        }
    }
}
这劳什子有何用?!

T存在的意义

public class Holder<T> {
    private T obj; //在编译时,该类中的所有的T都会被替换为边界类型Object。
    public void set(T obj){ this.obj = obj; }
    public T get(){ return obj; }
    public static void main(String[] args){
        Holder<Integer> holder = new Holder<>();
        //编译器会检查实参是不是一个Integer,
        //虽然这里的1是int类型,但是因为自动包装机制的存在,
        //他会被转化为一个Integer,因此能够通过类型检查。
        holder.set(1); 
        //编译器也会进行类型检查,
        //并且自动插入一个Object类型到Integer类型的转型操作。
        Integer obj = holder.get();
    }       
}

看看字节码

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #3                  // class Holder
         3: dup
         4: invokespecial #4                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: iconst_1
        10: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        13: invokevirtual #6                  // Method set:(Ljava/lang/Object;)V
        16: aload_1
        17: invokevirtual #7                  // Method get:()Ljava/lang/Object;
        20: checkcast     #8                  // class java/lang/Integer
        23: astore_2
        24: return

泛型的实际实现

  • 对泛型的处理全部集中在编译期,在编译时,编译器会执行如下操作。
    • 会将泛型类的类型参数都用边界类型替换。
    • 对于传入对象给方法形参的指令,编译器会执行一个类型检查,看传入的对象是不是类型参数所指定的类型。
    • 对于返回类型参数表示对象的指令,也会执行一个类型检查,还会插入一个自动的向下转型,将对象从边界类型向下转型到类型参数所表示的类型。

这些都是错的 

ppublic class Holder<T> {
    private T obj;
    public void set(T obj){ this.obj = obj; }
    public T get(){ return obj; }
    public void testT(Object arg){
        if (arg instanceof T){ ... } //编译错误
        T var = new T(); //编译错误
        T[] array = new T[100]; //编译错误
        }
    }
}

如果真想生成泛型对象?

class Holder<T>{
    private T t;
    public void init(IFactory<T> factory){
        this.t = factory.create();  // 此处即为new T()的工厂方法的实现
    }
}
interface IFactory<T>{  //接口也可以参数化
    T create();
}
class IntegerFactory implements IFactory<Integer>{
    public Integer create(){
        return new Integer(10);
    }
}
public class newTwithFactory{
    public static void main(String[] args){
        Holder<Integer> holder = new Holder<>();
        holder.init(new IntegerFactory());
    }
}

或者可以使用RTTI

class Holder<T>{
    private T t;
    private Class<T> kind;
    public Holder(Class<T> kind){ this.kind = kind; }
    public void init(){
        try{
            this.t = kind.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Holder<Integer> holder = new Holder<>(Integer.class);
        holder.init();
    }
}

边界 Bounds

extends申明对参数类型的限制条件

interface HasColor{ java.awt.Color getColor(); }

class Colored <T extends HasColor>{...}

class Dimension { public int x,y,z; }

class ColoredDimension <T extends HasColor & Dimension>{...} //错误!
class ColoredDimension <T extends Dimension & HasColor>{ //why?
    
}

看看这个例子


class Fruit{}
class Apple extends Fruit{}

public class NonConvariantGeneric {
    List<Fruit> flist = new ArrayList<Apple>(); //编译错误
}

Apple的List不是Fruit的List。Apple的List将持有Apple和Apple的子类型,Fruit的List将持有任何类型的Fruit。是的,这包括Apple,但是它不是一个Apple的List,它仍然是Fruit的List。Apple的List在类型上不等价于Fruit的List,即使Apple是一种Fruit类型。

再看看这个例子

class Fruit {}
class Apple extends Fruit {}

class Plate<T>{
    private T item;
    public Plate(T t){item=t;}
    public void set(T t){item=t;}
    public T get(){return item;}
}

//现在我定义一个“水果盘子”,逻辑上水果盘子当然可以装苹果。
//但实际上Java编译器不允许这个操作。会报错,“装苹果的盘子”无法转换成“装水果的盘子”。
Plate<Fruit> p = new Plate<Apple>(new Apple()); //编译错误!
“苹果” IS-A “水果”, BUT “装苹果的盘子” NOT-IS-A “装水果的盘子”!

协变与逆变

协变与逆变(Covariance and contravariance )是在计算机科学中,描述具有父/子型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有父/子型别关系的用语。

https://zh.wikipedia.org/wiki/协变与逆变

通配符

class Fruit{}
class Apple extends Fruit{}
public class GenericsAndCovariance {
    public static void main(String[] args){
        //一个能放水果以及一切是水果派生类的盘子,啥水果都能放的盘子
        //Plate<? extends Fruit>和Plate<Apple>最大的区别就是:
        //Plate<? extends Fruit>是Plate<Fruit>以及Plate<Apple>的基类。
        Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
        // a list of any type that's inherited from Fruit
        List<? extends Fruit> flist = new ArrayList<Apple>();
    }
}

扩展一下

class Food{}
//Lev 2
class Fruit extends Food{}
class Meat extends Food{}
//Lev 3
class Apple extends Fruit{}
class Banana extends Fruit{}
class Pork extends Meat{}
class Beef extends Meat{}
//Lev 4
class RedApple extends Apple{}
class GreenApple extends Apple{}

https://www.zhihu.com/question/20400700

通配

but

class Fruit{}
class Apple extends Fruit{}

       Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
        //不能存入任何元素
        p.set(new Fruit());    //Error
        p.set(new Apple());    //Error
        //读取出来的东西只能存放在Fruit或它的基类里。
        Fruit newFruit1=p.get();
        Object newFruit2=p.get();
        Apple newFruit3=p.get();    //Error
"A Plate of any type that's inherited from Fruit" 的意思不是"A Plate will hold any type of Fruit" but means "some specific type which is not specified"

super

表达的就是相反的概念:一个能放水果以及一切是水果基类的盘子。

Plate<?super Fruit>覆盖图中红色的区域。

but

class Fruit{}
class Apple extends Fruit{}
public class GenericsAndCovariance {
    public static void main(String[] args){
       Plate<? super Fruit> p=new Plate<Fruit>(new Fruit());
        //存入元素正常
        p.set(new Fruit());
        p.set(new Apple());
        //读取出来的东西只能存放在Object类里。
        Apple newFruit3=p.get();    //Error
        Fruit newFruit1=p.get();    //Error
        Object newFruit2=p.get();
    }
}

区别?

///无界通配符 Unbounded Wildcards
List<?>
List
List<Object>

Overloading

public class UseList<W,T>{
    void f(List<T> v){}
    void f(List<W> v){}
}
NO!

Self-bounded types 😢

class SelfBounded<T extends SelfBounded<T>>{
    ...
}

https://stackoverflow.com/questions/19588449/self-bounded-generics

https://jishu.dev/2021/03/03/java-self-bounded-types/

从简单的开始

public class BasicHolder<T> {
    T element;
    void set(T arg){this.element = arg;}
    T get(){return this.element;}
    void print(){
        System.out.println(element.getClass().getSimpleName());
    }
}
public class SubType extends BasicHolder<SubType> {
    public static void main(String[] args){
        SubType s1 = new SubType();
        SubType s3 = s1.get();
        s1.print();
        s3.print();
    }
}

得到什么?

public class SubType extends BasicHolder<SubType> {}
public class Plate extends BasicHolder<Plate>{}
...

... the generic base class becomes a kind of template for comon functionality for all its derived class


跟实现一个父类有什么区别?

跟普通继承关系的区别

... but this functionality will use the derived type for all of its arguments and return values ...


漂亮!

还有一点小问题

class Other{}
class BasicOther extends BasicHolder<Other>{}

public static void main(String[] args){
    BasicOther b = new BasicOther(), b2 = new BasicOther();
    b.set(new Other());
    Other other = b.get();
    b.print();// Other
}

没完全限制

Self-bounded

//forcing the generic to be used as its own bound argument
class SelfBounded<T extends SelfBounded<T>> {
    T element;
    SelfBounded<T> set(T arg){
        element = arg;
        return this;
    }
    T get(){return element;}
}

class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} //ok 
class D;
class E extends SelfBounded<D>{} //error

public static void main(String[] args){
        A a = new A();
        a.set(new A());
        a.print();
        B b = new B(), a2 = new B();
        //b.set(b2); //Error
        //b.print();
    }

高大上一点的说法





Argument covariance
&
Covariant return types