本文首发于个人网站:Java中的泛型(一)
本文主要介绍Java泛型的基本知识,包括目的、泛型类的基本用法和场景应用场景。
目的
- 编写更加“泛化”的代码,编写可以应付多重类型的代码
- Java中的泛型,用于实现“参数化类型”的概念
- 创造可以放不同类型对象的容器类,通过编译器来保证类型的正确;
- 能够简单&安全得创建复杂的模型
泛型类
定义
利用Java开发的时候,肯定会有一个类持有另一个或几个类的情况,在编写一些比较基础的组件,例如缓存操作组件,这类组件的逻辑差不多,但是希望能够处理不同的类型。
在Java SE5之前,我们可以通过Object这个类型来实现,举个例子:
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泛型实现,则如下所示:
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,还可以持有三个相同类型(或不同类型的)类型参数,代码如下:
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的概念。
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;}@Overridepublic String toString() {//这里隐含表示了:元祖中的元素是有顺序的return "(" + first + ", " + second + ")";}}
这个例子中的final关键字用的非常漂亮,有两个设计上的考虑
- 访问安全,客户端可以读取first和second两个对象,但是无法修改它们;
- 体现Object Value的含义,如果开发者需要一个不同元素的元组,必须重新构建一个;
利用继承机制,我们还可以实现元素更多的元组,下面的代码展示了三元组和四元组的实现。不过,从另一个角度考虑——如果一个方法调用需要返回四个以上元素的元组,是不是需要考虑下这个方法本身的设计是否合理了呢。
三元组
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;}@Overridepublic String toString() {return "(" + first + ", " + second + ", " + third + ")";}}
四元组
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;}@Overridepublic String toString() {return "(" + first + ", " + second + ", " + third + "," + forth + ")";}}
堆栈
再看一个比元祖复杂一点的例子,这里利用自己实现的链表存储机制构建了一个下推堆栈。
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的代码,说明内部类可以访问外部类的类型参数,我自己试了下,代码如下:
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,或者我们可以用它决定每天中午吃什么😜。
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到作者想要表达的意思。
本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。

