【Java】《OnJava8》笔记——第20章泛型

都是个人学习过程的笔记,不是总结,没有参考价值,但是这本书很棒

简单泛型

一个元组类库

一个堆栈类

RandomList

代码有点错误
不应该是Array.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add);
而应该是Arrays.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add);
当然这也就是个使用流和函数式编程的例子

泛型接口

当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。生成器无需额外的信息就知道如何创建新对象。

举个例子,Supplier对象不需要什么配置信息,而工厂模式构建新对象需要一些配置信息。

泛型方法

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);
        gm.f(1.0);
        gm.f(1.0F);
        gm.f('c');
        gm.f(gm);
    }
}

可以看到上面的泛型方法并不依赖于泛型类,这也印证了书中所说

类本身可能是泛型的,也可能不是,不过这与它的方法是否是泛型的并没有什么关系。

当然你也的确可以写成下面这样,但这样的话方法前面的泛型参数就没有意义了,和类型上的泛型一致

class GenericMethods2 <T> {
    <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }
}

当然下面代码还是有意义的

class GenericMethods2 <T> {
    <R> void f(R x) {
        System.out.println(x.getClass().getName());
    }
}

当然书里还有一句话是

如果方法是 static 的,则无法访问该类的泛型类型参数,因此,如果使用了泛型类型参数,则它必须是泛型方法。

看下面代码,这段代码是无法编译的,具体原因和泛型擦除应该有关

class GenericMethods2<T> {
//    无法编译
    static void f(T x) {
        System.out.println(x.getClass().getName());
    }
}

而下面这段代码就没问题了

class GenericMethods2 {
    static <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }
}

变长参数和泛型方法

一个泛型的 Supplier

简化元组的使用

一个Set工具类

EnumSet.range的功能是从枚举对象里的一定范围内元素生成一个集合

构建复杂模型

这一节里的那个商店代码非常好非常好,不难理解,Suppliers工具类编写得也特别好,那里用到了一个复杂的三个参数的collect方法,在流那一章介绍过
直观上理解,这个类的功能就是对容器类型进行操作,create方法是构造一个有元素的容器,元素的生成是通过Supplier和流的generate方法创建的,剩下两个方法类似

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class Suppliers {
    // Create a collection and fill it:
    public static <T, C extends Collection<T>> C
    create(Supplier<C> factory, Supplier<T> gen, int n) {
        return Stream.generate(gen)
                .limit(n)
                .collect(factory, C::add, C::addAll);
    }

    // Fill an existing collection:
    public static <T, C extends Collection<T>>
    C fill(C coll, Supplier<T> gen, int n) {
        Stream.generate(gen)
                .limit(n)
                .forEach(coll::add);
        return coll;
    }

    // Use an unbound method reference to
    // produce a more general method:
    public static <H, A> H fill(H holder,
                                BiConsumer<H, A> adder, Supplier<A> gen, int n) {
        Stream.generate(gen)
                .limit(n)
                .forEach(a -> adder.accept(holder, a));
        return holder;
    }
}

泛型擦除

高潮来了,核心就是一句话

在泛型代码内部,无法获取任何有关泛型参数类型的信息。因此,你可以知道如类型参数标识符和泛型边界这些信息,但无法得知实际的类型参数从而用来创建特定的实例。
这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。

意思就是,你只知道<T>是一个类,但是永远无法知道它具体是什么,也无法创建一个T对象,更别说获取这个对象的方法

public class HasF {
    public void f() {
        System.out.println("HasF.f()");
    }
}
public class Manipulator2<T extends HasF> {
    private T obj;

    Manipulator2(T x) {
        obj = x;
    }

    public void manipulate() {
        obj.f();
    }
}

边界 <T extends HasF> 声明 T 必须是HasF类型或其子类。如果情况确实如此,就可以安全地在obj上调用f()方法。
我们说泛型类型参数会擦除到它的第一个边界(可能有多个边界,稍后你将看到)。我们还提到了类型参数的擦除。编译器实际上会把类型参数替换为它的擦除,就像上面的示例,T 擦除到了 HasF,就像在类的声明中用 HasF 替换了 T 一样。如下所示

class Manipulator3 {
    private HasF obj;

    Manipulator3(HasF x) {
        obj = x;
    }

    public void manipulate() {
        obj.f();
    }
}

例如,如果某个类有一个返回 T 的方法,那么泛型就有所帮助,因为它们之后将返回确切的类型

也就是说我现在不止是想像Manipulator3那样返回一个HasF,,我想直接返回其确定的类型

// generics/ReturnGenericType.java

public class ReturnGenericType<T extends HasF> {
    private T obj;

    ReturnGenericType(T x) {
        obj = x;
    }

    public T get() {
        return obj;
    }
}

迁移兼容性

一句话就是,为了保障之前的没泛型的代码还能运行,java就得实现擦除,当某个类库变为泛型时,不会破坏依赖于它的代码和应用

擦除的问题

记住下面的话

泛型不能用于显式地引用运行时类型的操作中,例如转型、instanceof 操作和 new 表达式。因为所有关于参数的类型信息都丢失了,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有有关参数的类型信息而已。

class Foo<T> {
    T var;
}
Foo<Cat> f = new Foo<>();

class Foo 中的代码应该知道现在工作于 Cat 之上。泛型语法也在强烈暗示整个类中所有 T 出现的地方都被替换,就像在 C++ 中一样。但是事实并非如此,当你在编写这个类的代码时,必须提醒自己:“不,这只是一个 Object“。

// generics/ErasureAndInheritance.java

class GenericBase<T> {
    private T element;

    public void set(T arg) {
        element = arg;
    }

    public T get() {
        return element;
    }
}

class Derived1<T> extends GenericBase<T> {}

class Derived2 extends GenericBase {} // No warning

// class Derived3 extends GenericBase<?> {}
// Strange error:
// unexpected type
// required: class or interface without bounds
public class ErasureAndInteritance {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        Derived2 d2 = new Derived2();
        Object obj = d2.get();
        d2.set(obj); // Warning here!
    }
}

Derived1里面的泛型指的就是父类中的泛型,当然你也可以写成class Derived1<R> extends GenericBase<R> {}总之你知道两个泛型一致就行了。
至于Derived2,记住:“不,这只是一个 Object”
至于Derived3,强行编译的话,编译器说得很明白,需要无边界的类或者接口
在这里插入图片描述

但如果你写的是class Derived3 extends GenericBase<T> {},仍然会报错,报错理由也好理解,这样写的话Derived3就没有泛型了,那么就会造成运行过程中永远不知道T到底是个什么类型,想象我们如果写了一个Derived3 d3=new Derived3();d3.returnT();,那里面的returnT返回的到底个什么永远没法知道了。所以要么写成,class Derived3 extends GenericBase<String> {}直接指定T就是String,要么就像Derived1那样子类也保留泛型类。
在这里插入图片描述

边界处的动作

我突然好奇,泛型可不可以用来进行强制类型转换,答案是可以

@SuppressWarnings("unchecked")
    T[] create(int size) {
        return (T[]) Array.newInstance(kind, size);
    }

    T change(R r){
        return (T)r;
    }

FilledList.java这个代码里面括号有点错误

即使编译器无法得知 add() 中的 T 的任何信息,但它仍可以在编译期确保你放入 FilledList 中的对象是 T 类型。因此,即使擦除移除了方法或类中的实际类型的信息,编译器仍可以确保方法或类中使用的类型的内部一致性。

这句话进一步证明了,使用泛型的之后,只知道他是一个类
那个SimpleHolder.java和GenericHolder2.java的程序码对比简直绝了,太棒了,高潮有两个

  1. 首先,两段代码的getset方法的字节码一毛一样,尤其set方法里,接受的参数类型都是Object,这就说明泛型被擦除了,在里面实际操作的时候操作的就是Object,当然我也知道也无法对泛型对象也操作不了什么东西。
  2. 主函数里,SimpleHolder.java在调用holder.set("Item");方法时候,对应字节码11: invokevirtual #6; // Method set:(Object;)V看注释可以知道,在调用之前,对“Item”进行了类型转换成Object,而get的时候使用的是String s = (String) holder.get();,对应的字节码是15: invokevirtual #7; // Method get:()Object;18: checkcast #8; // class java/lang/String,也就是获取的是Object然后转型成String;然后我们惊奇的发现GenericHolder2.java反编译之后的程序和SimpleHolder.java一模一样,所以实际上做的事儿也是经历了两次类型转换

产生了相同的字节码,这就告诉我们泛型的所有动作都发生在边界处——对入参的编译器检查和对返回值的转型。这有助于澄清对擦除的困惑,记住:“边界就是动作发生的地方”。

补偿擦除

// generics/ClassTypeCapture.java

class Building {
}

class House extends Building {
}

public class ClassTypeCapture<T> {
    Class<T> kind;

    public ClassTypeCapture(Class<T> kind) {
        this.kind = kind;
    }

    public boolean f(Object arg) {
        return kind.isInstance(arg);
    }

    public static void main(String[] args) {
        ClassTypeCapture<Building> ctt1 =
                new ClassTypeCapture<>(Building.class);
        System.out.println(ctt1.f(new Building()));
        System.out.println(ctt1.f(new House()));
        ClassTypeCapture<House> ctt2 =
                new ClassTypeCapture<>(House.class);
        System.out.println(ctt2.f(new Building()));
        System.out.println(ctt2.f(new House()));
    }
}

这里就可以获得泛型类的信息了,但是注意,其实上面代码里使用的直接是传入的class信息才获得到的泛型类具体的类型信息,但我们仍然没法直接对泛型T进行操作,这也是补偿恢复的重要手段,就是把类字节码传递进去

创建类型的实例

泛型数组

// generics/ArrayOfGenericReference.java

class Generic<T> {
}

public class ArrayOfGenericReference {
    static Generic<Integer>[] gia;
}

// generics/ArrayOfGeneric.java

public class ArrayOfGeneric {
    static final int SIZE = 100;
    static Generic<Integer>[] gia;

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        try {
            gia = (Generic<Integer>[]) new Object[SIZE];
        } catch (ClassCastException e) {
            System.out.println(e.getMessage());
        }
        // Runtime type is the raw (erased) type:
        gia = (Generic<Integer>[]) new Generic[SIZE];
        System.out.println(gia.getClass().getSimpleName());
        gia[0] = new Generic<>();
        //- gia[1] = new Object(); // Compile-time error
        // Discovers type mismatch at compile time:
        //- gia[2] = new Generic<Double>();
    }
}
/* Output:
[Ljava.lang.Object; cannot be cast to [LGeneric;
Generic[]
*/

上面代码是泛型的数组
书上说

成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。

原因书上说:“在运行时,它仍然是一个 Object 数组”。但我不这么认为,这明明是个没泛型的Generic数组。

但是我们永远无法创建具有该确切类型(包括类型参数)的数组

// generics/GenericArray.java

public class GenericArray<T> {
    private T[] array;

    @SuppressWarnings("unchecked")
    public GenericArray(int sz) {
        array = (T[]) new Object[sz];
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T get(int index) {
        return array[index];
    }

    // Method that exposes the underlying representation:
    public T[] rep() {
        return array;
    }

    public static void main(String[] args) {
        GenericArray<Integer> gai = new GenericArray<>(10);
        try {
            Integer[] ia = gai.rep();
        } catch (ClassCastException e) {
            System.out.println(e.getMessage());
        }
        // This is OK:
        Object[] oa = gai.rep();
    }
}
/* Output:
[Ljava.lang.Object; cannot be cast to
[Ljava.lang.Integer;
*/

上面代码是泛型数组
Integer[] ia = gai.rep();会报错,这个的原因实际上就是:“这再次是因为实际的运行时类型为 Object[]”,Object是Integer的父类啊,所以不能Integer[] kids=new Object[10];
ArrayOfGeneric.java和GenericArray.java里的代码还是有差别的,前者是带泛型的类的数组,后者是泛型自身的数组

边界

这一章的内容很有意思
这一章里讲的extends关键字都是用在限定泛型类型时,也就是T extends R,而不是下一章所讲的? extends R
这一章的EpicBattle.java代码很有趣,也概括了边界的用法,简单应用不谈了,讲两个点

  1. class SuperSleuth<POWER extends XRayVision> extends SuperHero<POWER>而不能写成class SuperSleuth<POWER> extends SuperHero<POWER extends XRayVision >,理由和上面的Derived3类似,required: class or interface without bounds即需要的都是无边界的类型、接口(包括泛型)
  2. 泛型方法也是同理,例如<POWER extends SuperHearing> void useSuperHearing(SuperHero<POWER> hero),也不能写成<POWER> void useSuperHearing(SuperHero<POWER extends SuperHearing> hero)

在这里插入图片描述

通配符

// generics/CovariantArrays.java

class Fruit {}

class Apple extends Fruit {}

class Jonathan extends Apple {}

class Orange extends Fruit {}

public class CovariantArrays {

    public static void main(String[] args) {
        Fruit[] fruit = new Apple[10];
        fruit[0] = new Apple(); // OK
        fruit[1] = new Jonathan(); // OK
        // Runtime type is Apple[], not Fruit[] or Orange[]:
        try {
            // Compiler allows you to add Fruit:
            fruit[0] = new Fruit(); // ArrayStoreException
        } catch (Exception e) {
            System.out.println(e);
        }
        try {
            // Compiler allows you to add Oranges:
            fruit[0] = new Orange(); // ArrayStoreException
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}
/* Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
*/

fruit[]里放Orange或者Fruit类型都会在运行阶段报错,而编译阶段并不会报错。这是因为编译阶段,编译器只知道fruit是一个Fruit类型的列表,那么编译器自然会认为往这个列表里放Fruit类型的对象是可行的;但是在运行阶段,fruit实际引用的对象是一个Apple类型的列表,往里放其他类型的当然是不可行的了。

// generics/NonCovariantGenerics.java
// {WillNotCompile}

import java.util.*;

public class NonCovariantGenerics {
    // Compile Error: incompatible types:
    List<Fruit> flist = new ArrayList<Apple>();
}

就记住,这两者没有关系,编译阶段编译器就已经知道了这俩玩意不一致

尽管你在首次阅读这段代码时会认为“不能将一个 Apple 集合赋值给一个 Fruit 集合”。记住,泛型不仅仅是关于集合,它真正要表达的是“不能把一个涉及 Apple 的泛型赋值给一个涉及 Fruit 的泛型”。

// generics/GenericsAndCovariance.java

import java.util.*;

public class GenericsAndCovariance {

    public static void main(String[] args) {
        // Wildcards allow covariance:
        List<? extends Fruit> flist = new ArrayList<>();
        // Compile Error: can't add any type of object:
        // flist.add(new Apple());
        // flist.add(new Fruit());
        // flist.add(new Object());
        flist.add(null); // Legal but uninteresting
        // We know it returns at least Fruit:
        Fruit f = flist.get(0);
    }

}

我原来一直也不理解通配符到底有什么卵用,它意味着某种 flist 引用没有指定的具体类型只是知道是Fruit的子类。这就告诉了编译器,我们不在乎也不知道这个具体类型是什么,只知道是某个继承自Fruit的类型。也因此,没办法向这个列表里添加任何类型的变量了。而就像最后一行代码一样,取出是没什么问题的,以为flist里的元素,最差也是个Fruit类型的变量。

List<? extends Fruit> flist = new ArrayList<>();
List<Orange> olist = new ArrayList<>();
flist=olist;

当然,向上面代码那样赋值也是可行的,但同样也没法向flist添加东西,这就类似于完成了泛型的向上转型。再次理解为什么不能使用add了,上面代码里,flist里对象的具体类型丢失了,向其中调用add方法有可能是不安全的,因此被编译器禁止掉了。

当然你可能会说

List<? extends Fruit> flist = new ArrayList<>();
flist.add(new Fruit());

总安全吧,这有什么不安全的呢?当然不安全了啊,万一flist是一个new ArrayList<Orange>();,怎么可能往里添加Fruit呢?

编译器有多聪明

// generics/CompilerIntelligence.java

import java.util.*;

public class CompilerIntelligence {

    public static void main(String[] args) {
        List<? extends Fruit> flist = Arrays.asList(new Apple());
        Apple a = (Apple) flist.get(0); // No warning
        flist.contains(new Apple()); // Argument is 'Object'
        flist.indexOf(new Apple()); // Argument is 'Object'
    }

}

那如果我们使用了通配符,是不是就无法调用泛型类里的任何方法了呢?并不是。通过查看源码可以知道,containsindexOf方法接受的参数都是Object类型的,上一段代码里的get方法接受的是整数类型,这也就是说

因此当你指定一个 ArrayList<? extends Fruit> 时,add() 的参数(类型)就变成了"? extends Fruit"。编译器仅仅会拒绝调用像 add() 这样参数列表中涉及通配符的方法。
当然返回值是通配符泛型的方法还是可以调用的,例如get方法

逆变

可以声明:<?super T><?super MyClass>
不可以声明<T super MyClass>
含义就是,MyClass的某个父类

// generics/SuperTypeWildcards.java
import java.util.*;
public class SuperTypeWildcards {
    static void writeTo(List<? super Apple> apples) {
        apples.add(new Apple());
        apples.add(new Jonathan());
        // apples.add(new Fruit()); // Error

        Object object = apples.get(0);
        // Apple apple = apples.get(0);  // Error
        // List<Apple> realApples=apples; // Error
    }
}

书上代码并没有最后三行,我自己添加了点。superextends异曲同工,有三点不同,

  1. apples只知道持有的对象是Apple的某个父类,但并不知道具体是什么,但与extends不同的是,向apples添加Apple类型的任何子类一定是安全的,所以被编译器放过了。
  2. 但是当使用get的时候,他只能返回一个Object对象,除此之外返回任何其他类型的(例如Fruit)都有可能是不安全的,因为编译器咋知道它父类都有谁。
  3. 同样也无法向最后一行那样赋值,这一点与extends也相反,在extends里这样赋值是安全的,而在supper里是不安全的。因为List<Object>List<Fruit>没有半毛钱关系

自己概括一下这俩的使用时候的区别,extends类似于把泛型进行了向上转型为边界,但付出的代价是处理泛型对象的能力;super的功能类似于指定泛型的下界,付出的代价是失去了泛型的具体类型

无界通配符

原生类型就是new ArrayList();这种不带泛型的。

UnboundedWildcards1.java这段代码虽然注释了很多地方,但其实都是些warning,并不是error,因为只抛了一些warning出来,所以这段代码告诉我们:

编译器很少关心使用的是原生类型还是 <?>

UnboundedWildcards2.java讲的是?的使用功能之一

我是想用 Java 的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。

例如下面代码,写成<T> void show(List<T> list)当然也行,但是没意义

void show(List<?> list){
    System.out.println("yeah");
}

Wildcards.java代码是一个本节通配符内容的总结归纳,里面有warning和error,总之就是各种各样的规则

  1. rawArgs内部实际上是可以set一个Object类型的,只不过会有个warning而已
  2. unboundedArg内部不可以set一个Object类型的,因为即使使用的是无边界通配符,那这个泛型也是确定的某个泛型,不能随随便便把一个Object类型作为参数传递进去,这个机制与extends不能add类似
  3. wildSubtypewildSupertype不谈
  4. rawArgs方法的参数是原生类型,其主函数里的四次成功调用说明:原生类型对于参数的类型接受相当宽泛,带不带泛型带啥泛型都行
  5. unboundedArg方法的参数是无边界通配符泛型类,其主函数里的四次调用说明:无边界通配符类型对于参数的类型接受也相当宽泛,带不带泛型带啥泛型都行
  6. exact1方法参数是确定的泛型类参数,从r1r4可以看出,即使泛型类是确定的,原生、确定泛型、无边界泛型和有边界泛型都能传入(当然是在保证不产生冲突的前提下)
  7. r5讲的和第4点相同,r6没啥说的
  8. r7r8是两个error,对比r3r4却成功了,说明extract2的第二个参数导致了这个泛型T被唯一确定了,例如exact2(raw, lng);就导致了泛型类就是个Long而不是<?>或者<? extends Long>,这就引入了冲突。
  9. r9仍然说明原生类型万金油
  10. r10r12理所应当,没什么好说的,但r11也成功了,这一块我不太清楚为什么会成功,我个人理解是:由于使用的是? extends通配符,因此无法调用以? extends通配符为参数的方法,所以即使传入了无边界通配符类型的参数,运行过程中也不会产生什么问题,反正你没法调用,不会产生类型方面的错误,所以编译器给了通过;但反过来我们是可以获取以? extends通配符为返回类型的方法,而由于传入的是无边界,所以返回的只能是Object才能确保安全。
  11. wildSupertype(unbounded, lng);调用却失败了,个人理解是:由于使用的是? super通配符,因此是可以调用以? super通配符为参数的方法,而由于传入了无边界通配符类型的参数,一边是? super通配符为参数的方法,一边是无边界通配符,这就导致了类型冲突,所以直接被编译器禁止了。
  12. wildSupertype(bounded, lng);调用失败理所应当
  13. 这段代码解释里的一句话特别好:“这取决于是否想要从泛型参数中返回类型确定的返回值(就像在 wildSubtype()中看到的那样),或者是否想要向泛型参数传递类型确定的参数(就像在wildSupertype() 中看到的那样)

捕获转换

有一种特殊情况需要使用 <?>而不是原生类型。如果向一个使用 <?> 的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。

问题

任何基本类型不可以作为类型参数

实现参数化接口

// generics/MultipleInterfaceVariants.java
// {WillNotCompile}

interface Payable<T> {}

class Employee1 implements Payable<Employee1> {}
//
class Hourly extends Employee1 implements Payable<Hourly> {}

书上的代码是Employee但是这个类在别的类里被定义过了,所以我改了个名,上面的Hourly无法编译,但是下面的代码就可以了,上面代码有冲突,下面代码没冲突

interface Payable<T> {}

class Employee1 implements Payable<Employee1> {}
//
class Hourly extends Employee1 implements Payable<Employee1> {}

转型和警告

GenericCast.java代码很有趣啊

import java.util.*;
import java.util.stream.*;

class FixedSizeStack<T> {
    private final int size;
    private Object[] storage;
    private int index = 0;

    FixedSizeStack(int size) {
        this.size = size;
        storage = new Object[size];
    }

    public void push(T item) {
        if(index < size)
            storage[index++] = item;
    }

    @SuppressWarnings("unchecked")
    public T pop() {
        return index == 0 ? null : (T)storage[--index];
    }

    @SuppressWarnings("unchecked")
    Stream<T> stream() {
        return (Stream<T>)Arrays.stream(storage);
    }
}

public class GenericCast {
    static String[] letters = "ABCDEFGHIJKLMNOPQRS".split("");

    public static void main(String[] args) {
        FixedSizeStack<String> strings =
                new FixedSizeStack<>(letters.length);
        Arrays.stream("ABCDEFGHIJKLMNOPQRS".split(""))
                .forEach(strings::push);
        System.out.println(strings.pop().getClass().getSimpleName());
        strings.stream()
//                .map(s -> s + " ")
                .forEach(System.out::print);
    }
}

书上说 pop方法里实际上没进行任何转型,因为擦除的存在,运行过程中,里面处理的一直是个Object,正如上文所说,类型变换发生在边界处,并且是靠编译器完成的。
后边那个类型转换是什么狗屁

重载

永远考虑擦除

基类劫持接口

就是不要有泛型类的冲突

// generics/ComparablePet.java

public class ComparablePet implements Comparable<ComparablePet> {
    @Override
    public int compareTo(ComparablePet o) {
        return 0;
    }
}

// generics/RestrictedComparablePets.java

public class Hamster extends ComparablePet implements Comparable<ComparablePet> {

    @Override
    public int compareTo(ComparablePet arg) {
        return 0;
    }
}
// Or just:
class Gecko extends ComparablePet {
    public int compareTo(ComparablePet arg) {
        return 0;
    }
}

// generics/HijackedInterface.java
// {WillNotCompile}

class Cat extends ComparablePet implements Comparable<Cat> {
    // error: Comparable cannot be inherited with
    // different arguments: <Cat> and <ComparablePet>
    // class Cat
    // ^
    // 1 error
    public int compareTo(Cat arg) {
        return 0;
    }
}

上面的代码就造成了泛型类之间的冲突

自限定的类型

古怪的循环泛型

首先记得

Java 中的泛型关乎参数和返回类型。因此它能够产生使用导出类作为其(泛型)参数和返回类型的基类。

这里面的代码还算好理解,就是把导出类型作为基类的泛型

基类用导出类替代其(泛型)参数。

自限定

这是个有趣的用法,

// generics/SelfBounding.java

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> {} // Also OK

class C extends SelfBounded<C> {
    C setAndGet(C arg) { 
        set(arg); 
        return get();
    }
}

class D {}
// Can't do this:
// class E extends SelfBounded<D> {}
// Compile error:
//   Type parameter D is not within its bound

// Alas, you can do this, so you cannot force the idiom:
class F extends SelfBounded {}

public class SelfBounding {
    public static void main(String[] args) {
        A a = new A();
        a.set(new A());
        a = a.set(new A()).get();
        a = a.get();
        C c = new C();
        c = c.setAndGet(new C());
    }
}

// generics/NotSelfBounded.java

public class NotSelfBounded<T> {
    T element;
    NotSelfBounded<T> set(T arg) {
        element = arg;
        return this;
    }
    T get() { return element; }
} 

class A2 extends NotSelfBounded<A2> {}
class B2 extends NotSelfBounded<A2> {}

class C2 extends NotSelfBounded<C2> {
    C2 setAndGet(C2 arg) { 
        set(arg); 
        return get(); 
    }
}

class D2 {}
// Now this is OK:
class E2 extends NotSelfBounded<D2> {}

上面两段代码,前者SelfBounding.java使用了自限定,后者NotSelfBounded.java没有使用自限定。通过对比才能看懂自限定的功能:

自限定的参数有何意义呢?它可以保证类型参数必须与正在被定义的类相同。

解释一下SelfBounding.java代码:

  1. class SelfBounded<T extends SelfBounded<T>>要求泛型是个自身类型SelfBounded<T>的子类
  2. class A extends SelfBounded<A>代码当然没什么问题,因为A满足边界要求,它是SelfBounded<A>的子类,这个是自限定的主要用法
  3. class B extends SelfBounded<A>也没问题,因为A满足边界要求,它是SelfBounded<A>的子类,但这个不是自限定的主要用法,这个和普通的集成更像一点
  4. class E extends SelfBounded<D>失败了,因为D满足边界要求,它是SelfBounded<D>的子类

再解释一下NotSelfBounded.java代码:

  1. class E2 extends NotSelfBounded<D2>成功了,因为没有自限定的要求

两者结合就能看出来自限定的功能了:它可以保证类型参数必须与正在被定义的类相同(或者继承自某个已经被定义的自限定类)。你看class A就保证了里面的泛型都必须是A

参数协变

// generics/CovariantReturnTypes.java

class Base {}
class Derived extends Base {}

interface OrdinaryGetter {
    Base get();
}

interface DerivedGetter extends OrdinaryGetter {
    // Overridden method return type can vary:
    @Override
    Derived get();
}

public class CovariantReturnTypes {
    void test(DerivedGetter d) {
        Derived d2 = d.get();
    }
}

这种重写方法返回子类的模式叫做协变

// generics/OrdinaryArguments.java

class OrdinarySetter {
    void set(Base base) {
        System.out.println("OrdinarySetter.set(Base)");
    }
}

class DerivedSetter extends OrdinarySetter {
    void set(Derived derived) {
        System.out.println("DerivedSetter.set(Derived)");
    }
}

public class OrdinaryArguments {
    public static void main(String[] args) {
        Base base = new Base();
        Derived derived = new Derived();
        DerivedSetter ds = new DerivedSetter();
        ds.set(derived);
        // Compiles--overloaded, not overridden!:
        ds.set(base);
    }
}
/* Output:
DerivedSetter.set(Derived)
OrdinarySetter.set(Base)
*/

上述代码的ds对象有两个set方法,他们的参数类型不同,因此是重载

// generics/SelfBoundingAndCovariantArguments.java

interface SelfBoundSetter<T extends SelfBoundSetter<T>> {
    void set(T arg);
}

interface Setter extends SelfBoundSetter<Setter> {}

public class SelfBoundingAndCovariantArguments {
    void
    testA(Setter s1, Setter s2, SelfBoundSetter sbs) {
        s1.set(s2);
        //- s1.set(sbs);
        // error: method set in interface SelfBoundSetter<T>
        // cannot be applied to given types;
        //     s1.set(sbs);
        //       ^
        //   required: Setter
        //   found: SelfBoundSetter
        //   reason: argument mismatch;
        // SelfBoundSetter cannot be converted to Setter
        //   where T is a type-variable:
        //     T extends SelfBoundSetter<T> declared in
        //     interface SelfBoundSetter
        // 1 error
    }
}

书上说:编译器不能识别将基类型当作参数传递给 set() 的尝试,因为没有任何方法具有这样的签名。这里所谓签名就是这个方法的一些特征,比如接受的参数类型,因为s1.set方法只能接受Setter类对象,因此s1.set(sbs)传递一个父类进去当然不行了。

// generics/PlainGenericInheritance.java

class GenericSetter<T> { // Not self-bounded
    void set(T arg) {
        System.out.println("GenericSetter.set(Base)");
    }
}

class DerivedGS extends GenericSetter<Base> {
    void set(Derived derived) {
        System.out.println("DerivedGS.set(Derived)");
    }
}

public class PlainGenericInheritance {
    public static void main(String[] args) {
        Base base = new Base();
        Derived derived = new Derived();
        DerivedGS dgs = new DerivedGS();
        dgs.set(derived);
        dgs.set(base); // Overloaded, not overridden!
    }
}
/* Output:
DerivedGS.set(Derived)
GenericSetter.set(Base)
*/

上述代码发生的也是重载而不是重写

动态类型安全

这个是用来处理类型安全的讲解

// generics/CheckedList.java
// Using Collection.checkedList()

import java.util.*;
class Pet{}
class Cat extends Pet{}
class Dog extends Pet{}
public class CheckedList {
    @SuppressWarnings("unchecked")
    static void oldStyleMethod(List probablyDogs) {
        probablyDogs.add(new Cat());
    }

    public static void main(String[] args) {
        List<Dog> dogs1 = new ArrayList<>();
        oldStyleMethod(dogs1); // Quietly accepts a Cat
        List<Dog> dogs2 = Collections.checkedList(
            new ArrayList<>(), Dog.class);
        try {
            oldStyleMethod(dogs2); // Throws an exception
        } catch(Exception e) {
            System.out.println("Expected: " + e);
        }
        // Derived types work fine:
        List<Pet> pets = Collections.checkedList(
            new ArrayList<>(), Pet.class);
        pets.add(new Dog());
        pets.add(new Cat());
    }
}
/* Output:
Expected: java.lang.ClassCastException: Attempt to
insert class typeinfo.pets.Cat element into collection
with element type class typeinfo.pets.Dog
*/

我惊了,dogs1真的能插入一只猫,并且如果我尝试获取这只猫Dog d=dogs1.get(0);,会报错java.lang.ClassCastException: chapter20.Cat cannot be cast to chapter20.Dog,那么checkList的功能正如书上所说

受检查的集合(经过check的)在你试图插入类型不正确的对象时抛出 ClassCastException ,这与泛型之前的(原生)集合形成了对比,对于后者来说,当你将对象从集合中取出时,才会通知你出现了问题。

为什么能成功插入一只猫呢?因为oldStyleMethod接受的是原生集合,丢掉了泛型信息,编译器无法判定插入的是正确的还是错误的,只能认为这个泛型就是个Object,而又因为实际运行过程中操作的都是Object,然后插入这只猫的时候,也是当Object插入的,所以成功了。
为什么取出的时候失败了呢?因为泛型的类型转换发生在边界,运行期间列表里存的实际上都是Object,取出的时候要进行类型转换把他转换成Dog的时候,失败了。

泛型异常

就是继承自Exception类的泛型,没啥特殊的地方

混型

C++中的混型

与接口混合

使用装饰器模式

简单介绍:装饰器模式
实际上就是给一个对象增强功能
在Mixins.java文件里,为了将BasicImpTimeStampedSerialNumbered的功能统一到一个类里,我们创建了一个继承了一大堆东西的Mixin
装饰器的作用

装饰器经常用于满足各种可能的组合

Decoration.java文件的目标是这样的,我们现在有一个Basic类的对象,现在希望这个对象能够有点新的功能,希望他能有个像Mixins.java里的TimeStamped那样的功能,但又不希望弄出个新子类,怎么办?

// generics/decorator/Decoration.java

// {java generics.decorator.Decoration}
package generics.decorator;
import java.util.*;

class Basic {
    private String value;
    public void set(String val) { value = val; }
    public String get() { return value; }
}

class Decorator extends Basic {
    protected Basic basic;
    Decorator(Basic basic) { this.basic = basic; }
    @Override
    public void set(String val) { basic.set(val); }
    @Override
    public String get() { return basic.get(); }
}

class TimeStamped extends Decorator {
    private final long timeStamp;
    TimeStamped(Basic basic) {
        super(basic);
        timeStamp = new Date().getTime();
    }
    public long getStamp() { return timeStamp; }
}

class SerialNumbered extends Decorator {
    private static long counter = 1;
    private final long serialNumber = counter++;
    SerialNumbered(Basic basic) { super(basic); }
    public long getSerialNumber() { return serialNumber; }
}

public class Decoration {
    public static void main(String[] args) {
        TimeStamped t = new TimeStamped(new Basic());
        TimeStamped t2 = new TimeStamped(
            new SerialNumbered(new Basic()));
        //- t2.getSerialNumber(); // Not available
        SerialNumbered s = new SerialNumbered(new Basic());
        SerialNumbered s2 = new SerialNumbered(
            new TimeStamped(new Basic()));
        //- s2.getStamp(); // Not available
  }
}

思路就是,让TimeStamped持有一个Basic对象就可以了啊,而代码里使用了Decorator这个类来帮助他持有这个Basic对象。
所以,所谓装饰器模式,就是拒绝创建新子类,仅仅靠持有原有对象,在新类里加上点功能。
当然书里也说了装饰器的缺陷

只有最后一层的方法是可视的。明显的缺陷是它只能有效地工作于装饰中的一层

意思就是,以t2为例,主函数里的t2实际上和Mixin一样,拥有着三种功能,System.out.println(((SerialNumbered)t2.basic).getSerialNumber());就可以获得序列码了,但从方法签名上看,我们不知道他也能打印序列码的,我们只知道他除了能干Basic的活,就只能打印时间戳了

与动态代理混合

// generics/DynamicProxyMixin.java

import java.lang.reflect.*;
import java.util.*;
import onjava.*;
import static onjava.Tuple.*;

class MixinProxy implements InvocationHandler {
    Map<String, Object> delegatesByMethod;
    @SuppressWarnings("unchecked")
    MixinProxy(Tuple2<Object, Class<?>>... pairs) {
        delegatesByMethod = new HashMap<>();
        for(Tuple2<Object, Class<?>> pair : pairs) {
            for(Method method : pair.a2.getMethods()) {
                String methodName = method.getName();
                // The first interface in the map
                // implements the method.
                if(!delegatesByMethod.containsKey(methodName))
                    delegatesByMethod.put(methodName, pair.a1);
            }
        }
    }
    @Override
    public Object invoke(Object proxy, Method method,
      Object[] args) throws Throwable {
        String methodName = method.getName();
        Object delegate = delegatesByMethod.get(methodName);
        return method.invoke(delegate, args);
    }

    @SuppressWarnings("unchecked")
    public static Object newInstance(Tuple2... pairs) {
        Class[] interfaces = new Class[pairs.length];
        for(int i = 0; i < pairs.length; i++) {
            interfaces[i] = (Class)pairs[i].a2;
        }
        ClassLoader cl = pairs[0].a1.getClass().getClassLoader();
        return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs));
    }
}

public class DynamicProxyMixin {
    public static void main(String[] args) {
        Object mixin = MixinProxy.newInstance(
          tuple(new BasicImp(), Basic.class),
          tuple(new TimeStampedImp(), TimeStamped.class),
          tuple(new SerialNumberedImp(), SerialNumbered.class));
        Basic b = (Basic)mixin;
        TimeStamped t = (TimeStamped)mixin;
        SerialNumbered s = (SerialNumbered)mixin;
        b.set("Hello");
        System.out.println(b.get());
        System.out.println(t.getStamp());
        System.out.println(s.getSerialNumber());
    }
}
/* Output:
Hello
1494331653339
1
*/

就是创建了一个动态代理对象,这个对象持有了三个用于实现三种功能的对象,delegatesByMethod记录了方法名-持有方法的对象,但这样可能产生冲突,如果两个对象A,B之间的方法存在重载关系,那这两个方法的名称一样豆角func,如果map里记录的是func-A,而我想调用的是B对象的func,上述代码就会错误

详细来说,如果上面连个接口的方法如下所示

interface SerialNumbered { 
	long getSerialNumber(); 
	public void overload(String i);}
interface TimeStamped { long getStamp(); public void overload(int i);}

在上述动态代理代码里调用如下代码,就会报错

public class DynamicProxyMixin {
    public static void main(String[] args) {
        Object mixin = MixinProxy.newInstance(
                tuple(new BasicImp(), Basic.class),
                tuple(new TimeStampedImp(), TimeStamped.class),
                tuple(new SerialNumberedImp(), SerialNumbered.class));
        Basic b = (Basic) mixin;
        TimeStamped t = (TimeStamped) mixin;
        SerialNumbered s = (SerialNumbered) mixin;

        t.overload(2);
        s.overload("1");
    }
}

如下所示,说对象不是所声明类的一个实例,什么意思呢?当我们执行s.overload("1");的时候,实际上执行的是method.invoke(delegate, args);,而在上述代码里,delegate就是一个那个new TimeStampedImp(),而要调用这个对象的overload方法,这个方法需要的是int,传入的却是个字符串"1",那么java就会告诉我们对象"1"不是声明类int的一个实例,调用失败
在这里插入图片描述

最后最后newInstance方法可以稍微改改,SafeVarargs代表我们不会对变长参数进行修改,@SafeVarargs注解的使用

@SafeVarargs
public static Object newInstance(Tuple2<Object,Class<?>>... pairs) {
    Class<?>[] interfaces = new Class[pairs.length];
    for (int i = 0; i < pairs.length; i++) {
        interfaces[i] = pairs[i].a2;
    }
    ClassLoader cl = pairs[0].a1.getClass().getClassLoader();
    return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs));
}

Class<?>[] interfaces = new Class[pairs.length];不能写成Class<?>[] interfaces = new Class<?>[pairs.length];,范型数组那节讲过,你永远无法创建具有具体类型的数组

另外我还发现了无边界通配符的一点补充,我复用了Holder类,下面代码会编译错误,don’t know why

static void ho(Holder<Holder<?>> h){
}
public static void main(String[] args) {
		Holder<Holder> t=new Holder<>();
        ho(t);//编译错误,需要的是Holder<Holder<?>>,而并非Holder<Holder>
}

对缺乏潜在类型机制的补偿

反射

将一个方法应用于序列

反射提供了一些有用的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。

Java8 中的辅助潜在类型

// generics/DogsAndRobotMethodReferences.java

// "Assisted Latent Typing"
import typeinfo.pets.*;
import java.util.function.*;

class PerformingDogA extends Dog {
    public void speak() { System.out.println("Woof!"); }
    public void sit() { System.out.println("Sitting"); }
    public void reproduce() {}
}

class RobotA {
    public void speak() { System.out.println("Click!"); }
    public void sit() { System.out.println("Clank!"); }
    public void oilChange() {}
}

class CommunicateA {
    public static <P> void perform(P performer,
      Consumer<P> action1, Consumer<P> action2) {
        action1.accept(performer);
        action2.accept(performer);
    }
}

public class DogsAndRobotMethodReferences {
    public static void main(String[] args) {
        CommunicateA.perform(new PerformingDogA(),
          PerformingDogA::speak, PerformingDogA::sit);
        CommunicateA.perform(new RobotA(),
          RobotA::speak, RobotA::sit);
        CommunicateA.perform(new Mime(),
          Mime::walkAgainstTheWind,
          Mime::pushInvisibleWalls);
    }
}
/* Output:
Woof!
Sitting
Click!
Clank!
*/

尽管他们不需要实现通用接口Performs了,但还得实现Consumer接口。
上面的方法是非绑定方法调用,RobotA::speak代表着需要实现的方法接受一个RobotA对象,调用其speak方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值