大聪明教你学Java | 深入浅出聊 Java 泛型

4,623 阅读7分钟

前言

这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战

最近一段时间经常研究 Java 源码,在源码中经常可以看到泛型的影子,看到了泛型就想到了我以前那张懵逼的脸😮 什么是泛型?泛什么型?什么泛型? 一提到泛型,可能很多小伙伴都知道它,但是并不了解它,正好今天我就和大家分享一下我对泛型的理解,听小弟娓娓道来~ ✍

什么是泛型

百度上有很多关于泛型的解释,但是描述的内容都不是很好理解,俗话说“繁琐问题必有猥琐解法”,那么泛型的“猥琐”解法就来了👇

“泛型”,通过字面可以理解为广泛的类型(这个类型可以应用在类、接口、方法,所以说他广泛),既然它是广泛的类型,那么就保证了代码与它们能够操作的数据类型是拆分开的,并不会绑定在一起,这也就可以让同一套代码用于多种数据类型,使用泛型的好处就是保证了代码的复用性,降低了耦合度,当然也就提升了代码的可读性。

下面咱们结合一段代码来看看:

import java.util.ArrayList;
import java.util.List;

/**
 * 深入浅出聊 Java 泛型
 * @description: GenericDemo
 * @author: 庄霸.liziye
 * @create: 2021-12-20 10:47
 **/
public class GenericDemo {
    public static void main(String[] args) {
        List arrayList = new ArrayList();
        arrayList.add("我在人民广场吃着炸鸡");
        arrayList.add("卖炸鸡花了");
        arrayList.add(20);

        for(int i = 0; i< arrayList.size();i++){
            String item = (String)arrayList.get(i);
            System.out.println("item = " + item);
        }
    }
}

不出意外,这段代码抛出异常了~ 在这里插入图片描述 咱们这时候还没有指定 List 集合的类型,如果将 List 集合指定成 String、或者 Integer 类型的话,那么 IDEA 就直接提示代码存在问题了👇 在这里插入图片描述 所以在这种场景下,就需要用上泛型了~ 接下来就分别简单说说泛型类、泛型接口和泛型方法🍺

泛型类

咱们先写一个简单的泛型类 + 测试方法👇

/**
 * 简单泛型类
 * @description: Generic
 * @author: 庄霸.liziye
 * @create: 2021-12-20 11:33
 **/
public class Generic<T>{

    private T value;

    public Generic(T value) {
        this.value = value;
    }

    public T getValue(){
        return value;
    }
}
/**
 - 深入浅出聊 Java 泛型
 - @description: GenericDemo
 - @author: 庄霸.liziye
 - @create: 2021-12-20 10:47
 **/
public class GenericDemo {
    public static void main(String[] args) {
        Generic<Integer> genericInteger = new Generic<Integer>(20);
        Generic<String> genericString = new Generic<String>("我在人民广场吃着炸鸡");

        System.out.println("genericInteger = " + genericInteger.getValue());
        System.out.println("genericString = " + genericString.getValue());
    }
}

咱们执行一下测试方法,看看执行结果是什么样子的~ 在这里插入图片描述 这里有几点需要注意的是:

  • 泛型类中,T 可以随便写为任意标识,常见的标识有 T(Type)、E(Element)、K(Key)、V(Value) 等。
  • 泛型的类型参数只能是类类型(类类型说白了就是 Class 类型),不能是简单类型(如 int 等)。
  • 使用泛型类的时候,不一定要传入泛型类型实参,当传入非泛型类型实参时,泛型类中使用泛型的方法或成员变量定义的类型可以为任何类型。举个小例子👇

在这里插入图片描述

泛型接口

直接上代码✌

/**
 * 深入浅出聊 Java 泛型
 * @description: GenericDemo
 * @author: 庄霸.liziye
 * @create: 2021-12-20 10:47
 **/
public class GenericDemo {
    public static void main(String[] args) {
        IMessage<String> message = new MessageImpl<String>();
        message.printMessage("我在人民广场吃着炸鸡");
    }
}

/**
 * 定义泛型接口
 **/
interface IMessage<T>{
    public void printMessage(T t);
}

/**
 * 实现泛型接口
 **/
class MessageImpl<T> implements IMessage<T>{
    @Override
    public void printMessage(T t) {
        System.out.println(t);
    }
}

上面的代码是在实现泛型接口的类时未传入泛型实参的情况,在这种情况下有一点是需要注意的:在声明类的时候,需将泛型的声明也加在接口实现类中(class MessageImpl < T > implements IMessage< T >),如果在接口实现类中未声明泛型的话,编辑器则会直接报错,如下图所示: 在这里插入图片描述 咱们改造一下代码,再来看看第二种情况~

/**
 * 深入浅出聊 Java 泛型
 * @description: GenericDemo
 * @author: 庄霸.liziye
 * @create: 2021-12-20 10:47
 **/
public class GenericDemo {
    public static void main(String[] args) {
        IMessage<String> message =new MessageImpl();
        message.printMessage("我在人民广场吃着炸鸡");
    }
}

/**
 * 定义泛型接口
 **/
interface IMessage<T>{
    public void printMessage(T t);
}

/**
 * 实现泛型接口
 **/
class MessageImpl implements IMessage<String>{
    @Override
    public void printMessage(String t) {
        System.out.println(t);
    }
}

这次我们传入了泛型实参,虽然我们只创建了一个泛型接口,但是我们可以为T传入无数个实参,形成无数种类型的泛型接口。此时有一点是需要特别注意:当泛型类型传入了实参类型时,则所有使用泛型的地方都要替换成传入的实参类型。

泛型方法

/**
 * 深入浅出聊 Java 泛型
 * @description: GenericDemo
 * @author: 庄霸.liziye
 * @create: 2021-12-20 10:47
 **/
public class GenericDemo {
    public static void main(String[] args) {
        //显式赋值
        String p1 = GenericDemo.<String>printfArray("我在人民广场吃着炸鸡");
        //隐式赋值,常用此方式,可以不指定 <String>
        String p2 = GenericDemo.printfArray("我在人民广场吃着炸鸡");

        System.out.println("p1 = " + p1);
        System.out.println("p2 = " + p2);
    }

    /**
     * 泛型方法
     * @param message
     * @param <T>
     * @return
     */
    public static <T> T printfArray(T message) {
        return message;
    }
}

关于泛型方法,这里就需要多说几句了👇

① 泛型方法在调用方法的时候指明泛型的具体类型,和泛型类刚好相反(泛型类在实例化类的时候指明泛型的具体类型)。

②在上面写的泛型方法中,static 与 T(即返回值类型)中间 < T > 非常重要,可以理解为这就是泛型方法的一个标记,只有声明了< T > 的方法才是泛型方法。但是,在泛型类中的使用了泛型的成员方法并不是泛型方法

用上面定义的泛型类举个例子👀

/**
 * 简单泛型类
 * @description: Generic
 * @author: 庄霸.liziye
 * @create: 2021-12-20 11:33
 **/
public class Generic<T>{

    private T value;

    public Generic(T value) {
        this.value = value;
    }

    public T getValue(){
        return value;
    }
}

在泛型类中定义了一个 getValue() 方法,有些小伙伴可能会觉得 getValue() 方法是一个泛型方法,其实不然。虽然在方法中使用了泛型,但 getValue() 方法只是一个普通的成员方法,只不过他的返回值类型用的是声明泛型类时声明的泛型。

③ 与泛型类的定义一样,此处的 T 可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。 在这里插入图片描述 在这里插入图片描述

泛型的边界

在使用泛型的时候,我们还可以为传入的泛型类型实参进行边界的限制。

/**
 * 简单泛型类
 * @description: Generic
 * @author: 庄霸.liziye
 * @create: 2021-12-20 11:33
 **/
public class Generic<T extends Number>{

    private T value;

    public Generic(T value) {
        this.value = value;
    }

    public T getValue(){
        return value;
    }
}

先改造一下泛型类,为泛型添加边界,限制传入的类型实参必须是 Number (或其他)类型的子类。如果此时我们传入的参数类型是 String ,那么编辑器则会提示错误(String 类型并不是 Number 类型的子类)👇 在这里插入图片描述

泛型的擦除机制

通过上面的例子,相信大家也对泛型有了一个更深的理解,最后再简单说一下泛型的擦除机制。

import java.util.ArrayList;
import java.util.List;

/**
 * 深入浅出聊 Java 泛型
 * @description: GenericDemo
 * @author: 庄霸.liziye
 * @create: 2021-12-20 10:47
 **/
public class GenericDemo {
    public static void main(String[] args) {

        List<String> stringArrayList = new ArrayList<String>();
        List<Integer> integerArrayList = new ArrayList<Integer>();

        Class classStringArrayList = stringArrayList.getClass();
        Class classIntegerArrayList = integerArrayList.getClass();

        System.out.println(classStringArrayList == classIntegerArrayList);

    }
}

在这里插入图片描述 我们明明声明的是两个类型不同的 List 集合,为什么打印出来的结果是 true 呢? 这就是泛型的擦除机制。在运行过程中任何和类型有关的信息都会被擦除,在运行过程中中,ArrayList< String > 和 ArrayList< Integer > 的具体信息都被擦除成它们的原生类型也就是 ArrayList 类型。也就是说泛型的类型在逻辑上可以看成是多个不同的类型,实际上都是一样的。 再说的具体点:玩英雄联盟的时候,我们选择了“剑圣”这个英雄,无论我们使用了“暗影·易”、“天人合一·易”还是“源计划·易”,他只是看起来不一样了,其实它依然是“剑圣”这个英雄。

最后再说一句:在研究源码的时候会发现,泛型使用频率最高的场景就是在定义集合的时候,所以在我们日常的编码过程中,合理的使用泛型去定义集合可以大大的简化编码,同时还可以保证我们的代码有一个良好的可读性和可用性👍。

小结

本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨🙇‍

希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●'◡'●)

如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。

爱你所爱 行你所行 听从你心 无问东西