Java中的泛型(一)

本文主要介绍Java泛型的基本知识,包括目的、泛型类的基本用法和场景应用场景。

目的

  1. 编写更加“泛化”的代码,编写可以应付多重类型的代码
  2. Java中的泛型,用于实现“参数化类型”的概念
  3. 创造可以放不同类型对象的容器类,通过编译器来保证类型的正确;
  4. 能够简单&安全得创建复杂的模型

泛型类

定义

利用Java开发的时候,肯定会有一个类持有另一个或几个类的情况,在编写一些比较基础的组件,例如缓存操作组件,这类组件的逻辑差不多,但是希望能够处理不同的类型。

在Java SE5之前,我们可以通过Object这个类型来实现,举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package org.java.learn.generics;

/**
* 作用: User: duqi Date: 2017/11/19 Time: 15:45
*/
public class Holder2 {

private Object a;

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

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

public Object getA() {
return a;
}

public static void main(String[] args) {
Holder2 h2 = new Holder2(new Automobile());
//这里需要进行类型的强制转换
Automobile automobile = (Automobile) h2.getA();

h2.setA("Not a Automobile");
String s = (String) h2.getA();

h2.setA(1); //这里发生了自动装箱
Integer x = (Integer) h2.getA();
}
}

上面这段代码,确实满足了一个组件持有不同类型对象的需求,但是也有两个问题:(1)取出对象的时候,需要进行类型的强制转换;(2)同一个容器对象,可以随意存取不同类型的对象,有出错的风险。JavaSE5引入了“泛型”的概念,使得代码可以应用于多个类型,同时还能避免上述我说的两个问题,上面的代码,如果用Java泛型实现,则如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.java.learn.generics;

/**
* 作用: User: duqi Date: 2017/11/19 Time: 15:48
*/
public class Holder3<T> {

private T a;

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

public void setA(T a) {
this.a = a;
}

public T getA() {
return a;
}

public static void main(String[] args) {
Holder3<Automobile> h3 = new Holder3<>(new Automobile());
Automobile automobile = h3.getA(); //这里不需要强制转换类型

// h3.setA("Not an Automobile"); Error
// h3.setA(1); Error
}
}

上面的Holder3是持有一个类型参数T,还可以持有三个相同类型(或不同类型的)类型参数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package org.java.learn.generics;

/**
* 作用: User: duqi Date: 2017/11/19 Time: 15:51
*/
public class Holder4<T> {

private T a;
private T b;
private T c;

public Holder4(T a, T b, T c) {
this.a = a;
this.b = b;
this.c = c;
}

public T getA() {
return a;
}

public T getB() {
return b;
}

public T getC() {
return c;
}

public void setA(T a) {
this.a = a;
}

public void setB(T b) {
this.b = b;
}

public void setC(T c) {
this.c = c;
}

public static void main(String[] args) {
Holder4<Automobile> holder4 = new Holder4<>(new Automobile(),new Automobile(), new Automobile());

Automobile a = holder4.getA();
Automobile b = holder4.getB();
Automobile c = holder4.getC();
}
}

应用场景

元组

在实际开发中,常常会有“一次方法调用返回多个对象的需求”,我现在有时候会为每个返回值创建一个Class的写法,但是这块不太符合领域模型的思想——混淆了Entity和Object Value的概念。利用元组,可以实现Object Value的概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.java.learn.util;

/**
* 作用: User: duqi Date: 2017/11/19 Time: 16:18
*/
public class TwoTuple<A, B> {
public final A first;
public final B second;

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

@Override
public String toString() {
//这里隐含表示了:元祖中的元素是有顺序的
return "(" + first + ", " + second + ")";
}
}

这个例子中的final关键字用的非常漂亮,有两个设计上的考虑

  • 访问安全,客户端可以读取first和second两个对象,但是无法修改它们;
  • 体现Object Value的含义,如果开发者需要一个不同元素的元组,必须重新构建一个;

利用继承机制,我们还可以实现元素更多的元组,下面的代码展示了三元组和四元组的实现。不过,从另一个角度考虑——如果一个方法调用需要返回四个以上元素的元组,是不是需要考虑下这个方法本身的设计是否合理了呢。

三元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.java.learn.util;

/**
* 作用: User: duqi Date: 2017/11/19 Time: 16:32
*/
public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {

public final C third;

public ThreeTuple(A first, B second, C third) {
super(first, second);
this.third = third;
}

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

四元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.java.learn.util;

/**
* 作用: User: duqi Date: 2017/11/19 Time: 16:36
*/
public class FourTuple<A, B, C, D> extends ThreeTuple<A, B, C> {

public final D forth;

public FourTuple(A first, B second, C third, D forth) {
super(first, second, third);
this.forth = forth;
}

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

堆栈

再看一个比元祖复杂一点的例子,这里利用自己实现的链表存储机制构建了一个下推堆栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package org.java.learn.util;

/**
* 作用: User: duqi Date: 2017/11/19 Time: 16:40
*/
public class LinkedStack<T> {

private static class Node<U> {
U item;
Node<U> next;

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

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

/**
* 是否到达堆栈底部
*
* @return
*/
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 res = top.item;
if (!top.end()) {
top = top.next;
}
return res;
}

public static void main(String[] args) {
LinkedStack<String> lss = new LinkedStack<>();
for (String s: "Phasers on stun!".split(" ")) {
lss.push(s);
}
String s;
while ((s = lss.pop()) != null) {
System.out.println(s);
}
}
}

书中的练习题5:移除Node类上的类型参数,并修改LinkedStack.java的代码,说明内部类可以访问外部类的类型参数,我自己试了下,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package org.java.learn.util;

/**
* 作用: User: duqi Date: 2017/11/19 Time: 16:40
*/
public class LinkedStack2<T> {

/**
* 这里不能使用静态内部类
*/
private class Node {
T item;
Node next;

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

public Node(T item, Node next) {
this.item = item;
this.next = next;
}

/**
* 是否到达堆栈底部
*
* @return
*/
boolean end() {
return item == null || next == null;
}
}

/**
* 末端哨兵
*/
private Node top = new Node();
public void push(T item) {
top = new Node(item, top);
}
public T pop() {
T res = top.item;
if (!top.end()) {
top = top.next;
}
return res;
}

public static void main(String[] args) {
LinkedStack<String> lss = new LinkedStack<>();
for (String s: "Phasers on stun!".split(" ")) {
lss.push(s);
}
String s;
while ((s = lss.pop()) != null) {
System.out.println(s);
}
}
}

代码显示,静态内部类,无法访问其外部类的类型参数,但是非静态内部类可以。

RandomList

书中还给出一个随机列表的例子——一个持有特定类型对象的列表,select()方法可以随机取出列表中的一个元素。这是一个抽奖demo,或者我们可以用它决定每天中午吃什么😜。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package org.java.learn.generics;

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

/**
* 作用: 持有特定类型对象的列表,select()方法可以随机取一个元素
* User: duqi
* Date: 2017/11/19
* Time: 17:08
*/
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()));
}

public static void main(String[] args) {
RandomList<String> randomList = new RandomList<>();
for (String s: "The queick brown for jumped over the lazy brown dog".split(" ")) {
randomList.add(s);
}
for (int i = 0; i < 11; i++) {
System.out.print(randomList.select() + " ");
}
}
}

总结

泛型的东西很多,我之前看《Java核心技术》学过一遍,最近为了给团队的同学做分享,准备再结合《Java编程思想》复习一遍,发现《Java编程思想》这本书写得非常有深度,需要思考、实践和回味,才能准确得get到作者想要表达的意思。