【Java】泛型

理解泛型以及对泛型的应用。

【Java】泛型

By img Microanswer Create at:Jan 16, 2020, 3:38:07 PM 

Tags: Java 泛型 集合 自动转型

理解泛型以及对泛型的应用。


重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

一、泛型简介

泛型听名知意,它是和Java中数据类型有点关系的东西,它本身并不是某个类型。为了更好的使用它,必须弄明白它具体的设计理念。不妨做一个生活中的比喻来解释这样的设计理念:

  1. 果盘
    我们家里大厅茶几上基本上都会有一个果盘,每当来客人的时候,我们就会将各种水果放在果盘里。因为它是果盘,我们将水果放在里面这很正常,我们总不会把臭袜子放在里面,对吧!

  2. 钱包
    大多数情况下,我们钱包都是拿来装钱的,只要是个人,一看到钱包,就肯定想到它里面应该有钱。总不会把家庭作业塞钱包里嘛。

  3. 茶杯
    喝茶是常用的就是茶杯,茶杯里装的是茶水。虽然你可以把今晚炖的龙骨汤倒进去喝,但谁没事儿闲着会干这样的事情嘛,对吧!

可见泛型在生活中处处可见,它的出现就是为了让我们知道某一个东西是用来处理某类事物的,这就是泛型。我们在编写程序的时候常常出现一个工具方法返回了一个对象,但我们设计之初的时候此方法可能返回多种不同类型,而在泛型出现之前,我们就必须小心又小心去处理这些不同的类型。现在有了泛型,在我们编写代码的时候就完成了对返回类型直观说明,这就极大的避免了许多可能出现的隐藏bug。官方说法就是:Generics add stability to your code by making more of your bugs detectable at compile time. 翻译成中文(百度翻译)就是:泛型通过在编译时检测更多的错误来增加代码的稳定性。

二、代码说明

果盘,假设我们是用ArrayList来当果盘,在泛型出现以前,我们的果盘是这样的:

ArrayList fruits = new ArrayList();
fruits.add("苹果");
fruits.add("桃子");

String fr = (String)fruits.get(0);

很明显,我们虽然有了果盘,但是我们不知道果盘里到底是不是水果,我们只有在拿到一个水果之后试图将其强制认为是一个水果。如果代码穿插很多,代码量大,不知道这个位置拿到的是不是真的水果,这样强制处理难免会在某一个时刻出现错误。

庆幸现在我们有了泛型,有了泛型就相当于告诉了程序,我们的果盘里只能装水果,别的你不能装,这不就保证了获取的数据就一定是水果了嘛:

ArrayList<String> fruits = new ArrayList<>();
fruits.add("苹果");
fruits.add("桃子");

String fr = fruits.get(0);

通过在定义果盘的时候指定果盘能处理的数据类型<String>来指定我们的果盘只能容纳String类型的水果,当我们再次进行获取操作时,甚至都不需要再强制转换,受泛型限定,我们获取到的一定就是水果,不可能是其他的。

三、自己写支持泛型的类

上面我们提到的ArrayList是Java内置已经实现好了支持泛型用法。那么我们自己要写一个能够使用泛型技术的类又该如何实现呢。

现在不如我们来实现一个钱包,这个钱包你想要它装钱,它就只能装钱你想让它装别的也可以,满满的代码里都是注释,绝对看到高潮:

// Wallet.java

// 普通类定义名称后面,多写一个尖括号,里面写这个类要处理的数据的类型,
// 当然了,这个类型只是一个类型替代符号,那么在这个类里面的所有要用到这个类型
// 的地方,都使用这个符号就可以了。
// 如果有多个,你可以通过英文逗号分隔开。
// 现在我们就用大写字母T表示我们这个钱包支持一个数据类型,这个类型具体是什么类型,
// 在其它地方使用钱包时只需要指定其类型就可以了。
public class Wallet<T> {

    // 定义一个数组,放入的钱都在这个数组里面。这里就当我们的钱包只能放10张钱,
    // 为什么要定义成Object类型的数组?
    // 因为 Object 是所有类型的超类,而且我们这里的T是不能直接通过它进行 new T[10] 这样是错误的。
    private Object[] ts = new Object[10];


    // 通过put方法允许外界将钱放入钱包。
    // 而放入的参数类型,就正好使用我们类上面写的 T,
    // 这样一来,钱包被定义成放什么类型,就只能往put方法里
    // 传递什么类型的参数。
    public void put(T t) {

        // 内部实现就简单了,循环找一个空位子把钱放进去。
        for (int i = 0; i < ts.length; i++) {
            if (ts[i] == null) {
                ts[i] = t;
                return;
            }
        }

        // 如果运行到这里,肯定钱包被装满了。不管了。
    }


    // 从钱包里取出一张钱,可以看到方法返回类型被定义为了 T,
    // 这样一来就保证了别的地方使用这个方法的时候,返回值就
    // 直接使希望的类型,不用再进行强制转换了。
    public T get() {

        // 内部实现就简单的从数组后面往前面找,发现一张钱就立即返回。
        for (int i = ts.length - 1; i >= 0; i--) {
            if (ts[i] != null) {

                // 这里我们将返回类型强制转换为了我们定义的 T 类型,
                // 因为我们数组定义的时候是定义为了 Object 的,
                // 而put 方法保证了每次放入的一定是相同的 T 类型,
                // 所以我们取出的时候,将其强制转换为 T 类型是不会出问题的。
                return (T)ts[i];
            }
        }

        // 如果运行到这里,说明钱包里没有钱,直接返回null。
        return null;
    }

}

现在,不防试一试我们自己的钱包类Wallet:


public class Test {

    // 钱。
    static class Money {
        private int num;

        public Money(int num) {
            this.num = num;
        }
    }


    public static void main(String[] args) {

        // 创建一个钱包,并且只允许这个钱包防止 Money 类型的数据。
        Wallet<Money> wallet = new Wallet<>();

        // 放入2张钱。
        wallet.put(new Money(10));
        wallet.put(new Money(50));

        // 获取一张钱
        // 获取的这张钱都不用强制转换,就可以直接使用。
        // 通过泛型,这样就进一步减少了可能出现的程序错误。
        // 并且代码更加简洁清晰。
        Money money = wallet.get();
    }
}

四、类型限定

在我们声明泛型时,假如我们希望这个类型只能是某个类以及这个类的子类的类型,这时候我们可以这样来声明我们的类:


// 限定 类型 T 只能是 Money 类以及其子类。
class Wallet<T extends Money> {
    // ...
}

五、泛型方法

如果我们类上没有指定类型,仅在方法上也是可以写泛型方法的:


// 方法 make 定义个了一个类型 A ,并且方法返回参数被设定为 A
// 这样的方法可以被任何类型引用接收,将会自动将返回值转换为引用的类型
// (实际上就是里面某处进行了转换到A指定的处理)。
// 如果返回的值不能被转换为对应的引用类型,将会爆转换异常错误。
// 这种写法了解就好。
public static <A> A make() {
    return (A)new Money(100);
}

举例使用这个方法:

public static void main(String[] args) {
    Object o = make(); // Object 是所有类的超类,转换为 Object 没有问题。
    Money m = make(); // 我们方法里面返回的就是Money,这里用Money接收没问题。

    Test t = make(); // 编译时没有问题,当运行时就会出现转换错误,我们不能把返回的Money转为Test类型噻。
}
Full text complete, Reproduction please indicate the source. Help you? Not as good as one:
Comment(Comments need to be logged in. You are not logged in.)
You need to log in before you can comment.

Comments (0 Comments)