Java泛型基础(一)

作者: 程序熊大 | 来源:发表于2017-12-09 20:39 被阅读454次

    本文首发于个人网站:Java中的泛型(一)

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

    目的

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

    泛型类

    定义

    利用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;
        }
    
        @Override
        public 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;
        }
    
        @Override
        public 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;
        }
    
        @Override
        public 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到作者想要表达的意思。

    相关文章

      网友评论

      • zoterap:网站已经打不开了?
        程序熊大:@zhuyinbai 域名解析有点问题,已经处理掉了,感谢提醒

      本文标题:Java泛型基础(一)

      本文链接:https://www.haomeiwen.com/subject/wsppixtx.html