第十五章 泛型
标签: Java编程思想
一般的类和方法,只能使用具体类型,要么是基本类型,要么是自定义的类,如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。
Java SE5重大变化之一:泛型的概念。实现了类型参数化。
15.1 简单泛型
持有单个对象或持有Object类型对象的类:
package com.generics;
/**
* @author zhulongkun20@163.com
* @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 zhulongkun20@163.com
* @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 zhulongkun20@163.com
* @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 zhulongkun20@163.com
* @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 zhulongkun20@163.com
* @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 zhulongkun20@163.com
* @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 zhulongkun20@163.com
* @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 zhulongkun20@163.com
* @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 zhulongkun20@163.com
* @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 zhulongkun20@163.com
* @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 通配符
- 无边界通配符:?
- 固定上边界通配符:? extends XX
- 固定下边界通配符:? super XX
说明:
不能对List<?>使用add方法, 仅有一个例外, 就是add(null)。
List<?>也不能使用get方法, 只有Object类型是个例外。
List<? extends E>不能用get方法。
我们要记住这么几个使用原则, 有人将其称为PECS(即”Producer Extends, Consumer Super”, 网上翻译为”生产者使用extends, 消费者使用super”, 我觉得还是不翻译的好). 也有的地方写作”in out”原则, 总的来说就是:
- in或者producer就是你要读取出数据以供随后使用(想象一下List的get), 这时使用extends关键字, 固定上边界的通配符. 你可以将该对象当做一个只读对象;
- out或者consumer就是你要将已有的数据写入对象(想象一下List的add), 这时使用super关键字, 固定下边界的通配符. 你可以将该对象当做一个只能写入的对象;
- 当你希望in或producer的数据能够使用Object类中的方法访问时, 使用无边界通配符;