Java泛型基础(一)目的泛型类总结

本文首发于个人网站: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到作者想要表达的意思。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏工科狗和生物喵

【计算机本科补全计划】Java学习笔记(四) 修饰符

正文之前 今天总算是把那个党员谈话给弄完了,三个学弟轮番跟我来聊天,讲自己的入党动机啥的,看到他们就仿佛看到了大一的自己,原来当时面对学长,面对这类事情,会紧张...

3389
来自专栏java一日一条

Swift 中的内存管理详解

这篇文章是在阅读《The Swift Programming Language》Automatic Reference Counting(ARC,自动引用计数)...

1021
来自专栏专注 Java 基础分享

解析java泛型(一)

     对于我们java中的泛型,可能很多人知道怎么使用并且使用的还不错,但是我认为想要恰到好处的使用泛型,还是需要深入的了解一下它的各种概念和内部原理。...

2056
来自专栏女程序员的日常

值类型和引用类型的区别,struct和class的区别

C#值类型和引用类型 1、简单比较   值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。   值类型(value type):...

2351
来自专栏牛肉圆粉不加葱

[7] - trait

这是我以前在知乎上看到关于类继承作用的回答,虽不完全正确,却十分明确的表达出了好的代码应避免类继承而尽量使用类组合。Scala 显然也非常赞同这一点,以至于有了...

1052
来自专栏积累沉淀

Python快速学习第三天

第三天: 字典 什么是字典? 字典是Python语言中唯一的映射类型。 映射类型对象里哈希值(键,key)和指向的对象(值,value)是一对多的的关系,通常被...

1928
来自专栏lgp20151222

详解Java构造方法为什么不能覆盖,我的钻牛角尖病又犯了....

但是,看了输出,我就纳闷为什么,为什么第三行不是BigEgg2.Yolk(),不能覆盖吗?

1622
来自专栏Java后端技术

这个坑,是时候填上了~

​  这两天,在网上逛的时候,发现了如下的一道面试题,感觉还有蛮有意思的,要是不仔细看还真容易掉到坑里面。第一眼看起来比较绕,所以比较难理解。最终我跳出了这个坑...

711
来自专栏Android开发指南

6:异常处理

2998
来自专栏十月梦想

函数

                                            函数体

833

扫码关注云+社区

领取腾讯云代金券