读书笔记-《ON JAVA 中文版》-摘要22[第二十章 泛型-1]

文章目录

  • 第二十章 泛型
    • 1. 简单泛型
      • 1.1 简单泛型
      • 1.2 一个元组类库
    • 2. 泛型接口
    • 3. 泛型方法
      • 3.1 泛型方法
      • 3.2 变长参数和泛型方法
    • 4. 构建复杂模型

第二十章 泛型

普通的类和方法只能使用特定的类型:基本数据类型或类类型。如果编写的代码需要应用于多种类型,这种严苛的限制对代码的束缚就会很大。

多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。

拘泥于单一的继承体系太过局限,如果方法以接口而不是类作为参数,限制就宽松多了,只要实现了接口就可以。

即便是接口也还是有诸多限制。一旦指定了接口,它就要求你的代码必须使用特定的接口。而我们希望编写更通用的代码,能够适用“非特定的类型”,而不是一个具体的接口或类。

这就是泛型的概念,是 Java 5 的重大变化之一。在很多情况下,它可以使代码更直接更优雅。

1. 简单泛型

1.1 简单泛型

促成泛型出现的最主要的动机之一是为了创建集合类。

先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型:

package generics;

class Automobile {}
public class Holder1 {
    private Automobile a;

    public Holder1(Automobile a) {
        this.a = a;
    }

    Automobile get() {
        return a;
    }
}

这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到的每个类型都编写一个新的类。

在 Java 5 之前,我们可以让这个类直接持有 Object 类型的对象:

package generics;

public class ObjectHolder {
    private Object a;

    public ObjectHolder(Object a) {
        this.a = a;
    }

    public Object get() {
        return a;
    }

    public void set(Object a) {
        this.a = a;
    }

    public static void main(String[] args) {
        ObjectHolder h2 = new ObjectHolder(new Automobile());
        Automobile a = (Automobile) h2.get();
        h2.set("Not an Automobile");
        String s = (String) h2.get();
        h2.set(1);
        Integer x = (Integer) h2.get();
    }
}

与其使用 Object ,我们更希望先指定一个类型占位符,稍后再决定具体使用什么类型。要达到这个目的,需要使用类型参数,用尖括号括住,放在类名后面。然后在使用这个类时,再用实际的类型替换此类型参数。在下面的例子中, T 就是类型参数:

package generics;

public class GenericHolder<T> {
    private T t;

    public GenericHolder() {
    }

    public T get() {
        return t;
    }

    public void set(T t) {
        this.t = t;
    }

    public static void main(String[] args) {
        GenericHolder<Automobile> h3 = new GenericHolder<>();
        h3.set(new Automobile()); // 此处有类型校验
        Automobile a = h3.get(); // 无需类型转换
//        h3.set("Not an Automobile"); // 报错
//        h3.set(1); // 报错
    }
}

这就是 Java 泛型的核心概念:你只需告诉编译器要使用什么类型,剩下的细节交给它来处理。

1.2 一个元组类库

有时一个方法需要能返回多个对象。而 return 语句只能返回单个对象,解决方法就是创建一个对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。

但是有了泛型,我们就可以一劳永逸。同时,还获得了编译时的类型安全。

这个概念称为元组,它是将一组对象直接打包存储于单一对象中。可以从该对象读取其中的元素,但不允许向其中存储新对象(这个概念也称为 数据传输对象 或 信使 )。

下面是一个可以存储两个对象的元组:

package onjava;

public class Tuple2<A, B> {
    public final A a1;
    public final B a2;

    // 构造函数传入要存储的对象。这个元组隐式地保持了其中元素的次序。
    public Tuple2(A a1, B a2) {
        this.a1 = a1;
        this.a2 = a2;
    }

    public String rep() {
        return a1 + ", " + a2;
    }

    @Override
    public String toString() {
        return "(" + rep() + ")";
    }
}

以利用继承机制实现长度更长的元组。添加更多的类型参数就行了:

package onjava;

public class Tuple3<A, B, C> extends Tuple2<A, B> {
    public final C a3;

    public Tuple3(A a, B b, C c) {
        super(a, b);
        a3 = c;
    }

    @Override
    public String rep() {
        return super.rep() + ", " + a3;
    }
}
package onjava;

public class Tuple4<A, B, C, D> extends Tuple3<A, B, C> {
    public final D a4;

    public Tuple4(A a, B b, C c, D d) {
        super(a, b, c);
        a4 = d;
    }

    @Override
    public String rep() {
        return super.rep() + ", " + a4;
    }
}
package onjava;

public class Tuple5<A, B, C, D, E> extends Tuple4<A, B, C, D> {
    public final E a5;

    public Tuple5(A a, B b, C c, D d, E e) {
        super(a, b, c, d);
        a5 = e;
    }

    @Override
    public String rep() {
        return super.rep() + ", " + a5;
    }
}

演示需要,再定义两个类:

package generics;

public class Amphibian {
}

package generics;

public class Vehicle {
}

使用元组时,你只需要定义一个长度适合的元组,将其作为返回值即可。注意下面例子中方法的返回类型:

package generics;

import onjava.Tuple2;
import onjava.Tuple3;
import onjava.Tuple4;
import onjava.Tuple5;

public class TupleTest {
    static Tuple2<String, Integer> f() {
        return new Tuple2<>("hi", 47);
    }

    static Tuple3<Amphibian, String, Integer> g() {
        return new Tuple3<>(new Amphibian(), "hi", 47);
    }

    static Tuple4<Vehicle, Amphibian, String, Integer> h() {
        return new Tuple4<>(new Vehicle(), new Amphibian(), "hi", 47);
    }

    static Tuple5<Vehicle, Amphibian, String, Integer, Double> k() {
        return new Tuple5<>(new Vehicle(), new Amphibian(), "hi", 47, 11.1);
    }

    public static void main(String[] args) {
        Tuple2<String, Integer> ttsi = f();
        System.out.println(ttsi);
//        ttsi.a1="there"; // 编译错误,因为 final 不能重新赋值
        System.out.println(g());
        System.out.println(h());
        System.out.println(k());
    }
}

输出:

(hi, 47)
(generics.Amphibian@7699a589, hi, 47)
(generics.Vehicle@58372a00, generics.Amphibian@4dd8dc3, hi, 47)
(generics.Vehicle@6d03e736, generics.Amphibian@568db2f2, hi, 47, 11.1)

2. 泛型接口

泛型也可以应用于接口。例如 生成器,这是一种专门负责创建对象的类

一般而言,一个生成器只定义一个方法,用于创建对象。例如 java.util.function 类库中的 Supplier 就是一个生成器,调用其 get() 获取对象。 get() 是泛型方法,返回值为类型参数 T 。

package generics.coffee;

public class Coffee {
    private static long counter = 0;
    private final long id = counter++;

    @Override
    public String toString() {
        return getClass().getSimpleName() + " " + id;
    }
}

package generics.coffee;

public class Americano extends Coffee{
}

package generics.coffee;

public class Breve extends Coffee{
}

package generics.coffee;

public class Cappuccino extends Coffee{
}

package generics.coffee;

public class Latte extends Coffee{
}

package generics.coffee;

public class Mocha extends Coffee{
}

编写一个类,实现 Supplier 接口,它能够随机生成不同类型的 Coffee 对象:

package generics.coffee;

import java.util.Iterator;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class CoffeeSupplier
        implements Supplier<Coffee>, Iterable<Coffee> {
    private Class<?>[] types = {Latte.class, Mocha.class,
            Cappuccino.class, Americano.class, Breve.class};
    private static Random rand = new Random(47);

    public CoffeeSupplier() {
        System.out.println("1");
    }

    // For iteration:
    private int size = 0;

    public CoffeeSupplier(int sz) {
        System.out.println("2");
        size = sz;
    }

    @Override
    public Coffee get() {
        System.out.println("3");
        try {
            return (Coffee) types[rand.nextInt(types.length)].newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    class CoffeeIterator implements Iterator<Coffee> {
        int count = size;

        @Override
        public boolean hasNext() {
            System.out.println("4");
            return count > 0;
        }

        @Override
        public Coffee next() {
            System.out.println("5");
            count--;
            return CoffeeSupplier.this.get();
        }

        @Override
        public void remove() {
            System.out.println("6");
            throw new UnsupportedOperationException();
        }
    }

    @Override
    public Iterator<Coffee> iterator() {
        System.out.println("7");
        return new CoffeeIterator();
    }

    public static void main(String[] args) {
        Stream.generate(new CoffeeSupplier())
                .limit(5)
                .forEach(System.out::println);
        System.out.println("------------------");
        for (Coffee c : new CoffeeSupplier(5)) {
            System.out.println(c);
        }
    }
}

输出:

1
3
Americano 0
3
Latte 1
3
Americano 2
3
Mocha 3
3
Mocha 4
------------------
2
7
4
5
3
Breve 5
4
5
3
Americano 6
4
5
3
Latte 7
4
5
3
Cappuccino 8
4
5
3
Cappuccino 9
4

—PS:为了了解代码执行顺序,加了几个输出语句。迷糊的话,可以看下这个大佬的文章:字节面试官问:Iterator和erable有什么区别?

3. 泛型方法

3.1 泛型方法

泛型方法独立于类而改变方法。

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

要定义泛型方法,请将泛型参数列表放置在返回值之前,如下所示:

package generics;

public class GenericMethods {
    // PS: 泛型参数列表
    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);
    }
}

输出:

java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
java.lang.Character
generics.GenericMethods

对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。 这称为 类型参数推断

3.2 变长参数和泛型方法

泛型方法和变长参数列表可以很好地共存:

package generics;

import java.util.ArrayList;
import java.util.List;

public class GenericVarargs {
    @SafeVarargs
    public static <T> List<T> makeList(T... args) {
        List<T> result = new ArrayList<>();
        for (T item : args) {
            result.add(item);
        }
        return result;
    }

    public static void main(String[] args) {
        List<String> ls = makeList("A");
        System.out.println(ls);
        ls = makeList("A", "B", "C");
        System.out.println(ls);
        ls = makeList(
                "ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
        System.out.println(ls);
    }
}

输出:

[A]
[A, B, C]
[A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]

4. 构建复杂模型

泛型的一个重要好处是能够简单安全地创建复杂模型。例如,我们可以轻松地创建一个元组列表:

package generics;

import onjava.Tuple4;

import java.util.ArrayList;

public class TupleList<A, B, C, D> extends ArrayList<Tuple4<A, B, C, D>> {

    public static void main(String[] args) {
        TupleList<Vehicle, Amphibian, String, Integer> tl = new TupleList<>();
        tl.add(TupleTest2.h());
        tl.add(TupleTest2.h());
        tl.forEach(System.out::println);
    }
}

class TupleTest2 {
    public static <A, B, C, D> Tuple4<A, B, C, D> tuple(A a, B b, C c, D d) {
        return new Tuple4<>(a, b, c, d);
    }

    public static Tuple4<Vehicle, Amphibian, String, Integer> h() {
        return tuple(new Vehicle(), new Amphibian(), "hi", 47);
    }
}

输出:

(generics.Vehicle@306a30c7, generics.Amphibian@b81eda8, hi, 47)
(generics.Vehicle@68de145, generics.Amphibian@27fa135a, hi, 47)

这将产生一个功能强大的数据结构,而无需太多代码。

读书笔记-《ON JAVA 中文版》-摘要22[第二十章 泛型-1]_第1张图片
(图网,侵删)

你可能感兴趣的:(读书笔记,java,开发语言)