第十五章 泛型

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Youyou_0826/article/details/80678820

第十五章 泛型

标签: Java编程思想


一般的类和方法,只能使用具体类型,要么是基本类型,要么是自定义的类,如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。

Java SE5重大变化之一:泛型的概念。实现了类型参数化。

15.1 简单泛型

持有单个对象或持有Object类型对象的类:

package com.generics;

/**
 * @author [email protected]
 * @since 2018-06-11 11:44
 */
public class Holder {
    private Automobile automobile;

    public Holder(Automobile automobile) {
        this.automobile = automobile;
    }

    Automobile getAutomobile() {
        return automobile;
    }
}

class Automobile {
}

使用类型参数:

package com.generics;

/**
 * @author [email protected]
 * @since 2018-06-11 11:46
 */
public class Holder3<T> {
    private T t;

    public Holder3(T t) {
        this.t = t;
    }

    public T getT() {
        return t;
    }

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

    public static void main(String[] args) {
        Holder3<Automobile> holder3 = new Holder3<>(new Automobile());
        Automobile automobile = holder3.getT();
    }
}

15.2.1 一个元组类库

经常会有一次方法调用能返回多个对象的需求,但是return语句只能返回一个对象,此时可以创建一个对象,用它来持有想要返回的多个对象,这个概念称为元组,允许将一组对象直接打包存储于一个对象。

package com.generics;

/**
 * @author [email protected]
 * @since 2018-06-11 12:03
 */
public class TwoTuple<A, B> {
    public final A first;
    public final B second;

    public TwoTuple(A a, B b) {
        this.first = a;
        this.second = b;
    }

    @Override
    public String toString() {
        return "TwoTuple{" +
                "first=" + first +
                ", second=" + second +
                '}';
    }
}

客户端可以直接读取first和second,但是却无法改变他们的值,这比将first和second声明为public然后提供访问方法更加便捷。
可以利用继承机制实现更长的元组。

package com.generics;

/**
 * @author [email protected]
 * @since 2018-06-11 12:08
 */
public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
    public final C third;

    public ThreeTuple(A a, B b, C c) {
        super(a, b);
        this.third = c;
    }

    @Override
    public String toString() {
        return "ThreeTuple{" +
                "third=" + third +
                ", first=" + first +
                ", second=" + second +
                '}';
    }
}

为了使用元组,只需要创建一个长度适合的元组,将其作为方法的返回值即可。

15.2.2 一个堆栈类

package com.generics;

/**
 * @author [email protected]
 * @since 2018-06-11 12:14
 */
public class LinkedStack<T> {
    private static class Node<U> {
        private U item;
        Node<U> next;

        Node() {
            item = null;
            next = null;
        }

        Node(U item, Node<U> next) {
            this.item = item;
            this.next = next;
        }

        boolean end() {
            return item == null && next == null;
        }
    }

    private Node<T> top = new Node<>();

    public void push(T item) {
        top = new Node<>(item, top);
    }

    public T pop() {
        T result = top.item;
        if (!top.end()) {
            top = top.next;
        }
        return result;
    }
}

15.2.3 RandomList

假设我们需要一个持有特定类型对象的列表,每次调用其上的select方法时,可以随机选取一个元素。

package com.generics;

import java.util.ArrayList;
import java.util.Random;

/**
 * @author [email protected]
 * @since 2018-06-11 12:30
 */
public class RandomList<T> {
    private ArrayList<T> storage = new ArrayList<>();
    private Random random = new Random(47);

    public void add(T item) {
        storage.add(item);
    }

    public T select() {
        return storage.get(random.nextInt(storage.size()));
    }
}

15.3 泛型接口

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

package com.generics;

/**
 * @author [email protected]
 * @since 2018-06-13 12:57
 */
public class PeopleGeneratorTest implements PeopleGenerator<Student> {
    public static void main(String[] args) {
        PeopleGeneratorTest test = new PeopleGeneratorTest();
        System.out.println(test.generatorT());
    }

    @Override
    public Student generatorT() {
        return new Student();
    }
}

interface PeopleGenerator<T> {
    T generatorT();
}

class People {
    private String name;
    private Integer age;

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

class Student extends People {
}

class Teacher extends People {
}

15.4 泛型方法

一个类是否拥有泛型方法,与该类是否是泛型没有关系,要定义泛型方法,只需要将泛型参数列表置于返回值之前。

package com.generics;

/**
 * @author [email protected]
 * @since 2018-06-13 13:08
 */
public class GenericTest {
    private String name;
    private Integer age;

    public <T> T fun(T t) {
        System.out.println(t.getClass().getName());
        return t;
    }

    public GenericTest(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "GenericTest{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String[] args) {
        GenericTest genericTest = new GenericTest("john", 23);
        System.out.println(genericTest.fun(genericTest));
    }
}

当时用泛型类时候,必须在创建对象时指明参数类型的值,而是用泛型方法的时候则不用指明,编译器会推断出具体的类型,称为”类型参数推断”。

15.5 匿名内部类

泛型与匿名内部类结合使用。

package com.generics;

/**
 * @author [email protected]
 * @since 2018-06-13 13:19
 */
public class InnerClass {
}

interface GenerateObject<T> {
    T generateObject();
}

class CounterObject {
    private static int count = 0;
    private final long id = count++;

    private CounterObject() {
    }

    @Override
    public String toString() {
        return "CounterObject{" +
                "id=" + id +
                '}';
    }

    public static GenerateObject<CounterObject> generateObject() {
        return CounterObject::new;
    }

    public static void main(String[] args) {
        GenerateObject generateObject = CounterObject::generateObject;
        for (int i = 0; i < 3; i++) {
            System.out.println(generateObject.generateObject());
        }
    }
}

15.6 构建复杂模型

泛型的一个重要好处是可以简单而安全的创建复杂的模型。

15.7 擦除的神秘之处

当你更加深入的研究泛型时,你会发现有大量的初看起来是没有用的,尽管可以声明ArrayList.class,但是不能声明ArrayList.class。

package com.generics;

import java.util.ArrayList;

/**
 * @author [email protected]
 * @since 2018-06-13 13:39
 */
public class ErasedTypeEquivalence {
    public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        System.out.println(c1 == c2);
    }
}

output:

true

Process finished with exit code 0

在泛型代码内部,无法获得任何与泛型有关的的参数类型的信息。

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的 类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

15.10 通配符

  1. 无边界通配符:?
  2. 固定上边界通配符:? extends XX
  3. 固定下边界通配符:? super XX

说明:
不能对List<?>使用add方法, 仅有一个例外, 就是add(null)。
List<?>也不能使用get方法, 只有Object类型是个例外。
List<? extends E>不能用get方法。

我们要记住这么几个使用原则, 有人将其称为PECS(即”Producer Extends, Consumer Super”, 网上翻译为”生产者使用extends, 消费者使用super”, 我觉得还是不翻译的好). 也有的地方写作”in out”原则, 总的来说就是:

  1. in或者producer就是你要读取出数据以供随后使用(想象一下List的get), 这时使用extends关键字, 固定上边界的通配符. 你可以将该对象当做一个只读对象;
  2. out或者consumer就是你要将已有的数据写入对象(想象一下List的add), 这时使用super关键字, 固定下边界的通配符. 你可以将该对象当做一个只能写入的对象;
  3. 当你希望in或producer的数据能够使用Object类中的方法访问时, 使用无边界通配符;

猜你喜欢

转载自blog.csdn.net/Youyou_0826/article/details/80678820