本文主要介绍Java泛型的基本知识,包括目的、泛型类的基本用法和场景应用场景。
目的编写更加“泛化”的代码,编写可以应付多重类型的代码 Java中的泛型,用于实现“参数化类型”的概念 创造可以放不同类型对象的容器类,通过编译器来保证类型的正确; 能够简单&安全得创建复杂的模型 泛型类 定义利用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;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;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(); } }
上面的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;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;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;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;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;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; } 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;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; } 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;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到作者想要表达的意思。
Thoughts, stories and ideas.