【RE:布丁JAVA学习】这大概是史上最详细的JAVA泛型教程,不看后悔。

引言

【RE:布丁JAVA】是一个java小白布丁萨玛浑浑噩噩工作三年之后发现自己java基础不行而重新学习java的系列,喜欢的同学可以点个关注,如果有什么问题可以在评论区发表评论,谢谢🙏

愿:天下程序猿头发乌黑亮丽

概述

在我刚开始学习JAVA的时候经常看到 <T,K> <? extends Number> 等等类似的写法,当时就很疑惑,这些到底是什么为什么有的时候是’T’有的时候是’K’而有的时候是’?’,是有什么规则么?这些代表的又是什么呢?我想要使用可以吗?
下面就要我们带着这种种疑问来看下我们今天的主题泛型’

1、为什么要使用泛型

我们学习泛型,首先要明白我们为什么要使用泛型

举个有点不优雅例子

小A过生日举行了一个生意派对,于是他准备了一个箱子。告诉大家没人可以把放进去一个食物。到时候自己会把他们一个一个的吃掉。于是小B放了苹果、小C放了橘子。
但是,我放了一坨屎进去。
然后小A在生日派对上就吃了shit发生了不可以思议的事情。
于是小A之后再办这种事情的时候,会让自己的管家检查放进入的是不是食物。不是食物就是不让放进去。这样他就不会吃屎了。
而这个管家就可以认为是泛型。为了约束放入箱子东西的类型。

好了好了,看完上面这个例子同学们一定都知道了什么是泛型了,为什么使用泛型(为了防止吃shit )下面我们就开认真讲一下。

2、什么是泛型

同学们都知道在我们开发的时候错误可以分为两种,

编译时错误

在编译阶段由java编译器发现的错误。
编译时错误
如上图所示,编译器发现我们的代码错误,会提示我们,而我们开发人员必须要修改错误之后,才可以通过编译。这就是编译时错误

运行时错误

编译时未报错,在运行时抛出异常。
在这里插入图片描述
如上图所示在编译的时候,是不会曝出错误。但是运行的时候就会抛出ClassCastException的错误,这样就称之为运行时错误。
在这里插入图片描述

从JDK5开始所有的Java集合都采用了泛型的机制。在声明集合变量的时候,可以使用‘<>’指定集合中的元素类型。

例子如下图
在这里插入图片描述

所以说:

泛型就是为了把 ClassCastException 运行时错误转换成编译时错,是为了约束参数类型而诞生的。

泛型的主要用于泛型类泛型接口泛型方法泛型数组

下面就让我们一次来介绍以下。

3、泛型类&泛型接口

首先我们先来看一个正常的类,然后我们尝试把他改造成一个泛型类。


public class Printer {
    /**
     * 打印文字
     */
    Object printText;

    public Printer(Object printText) {
        this.printText = printText;
    }

    public Object getPrintText() {
        return printText;
    }

    public void setPrintText(Object printText) {
        this.printText = printText;
    }
    
    public static void main(String[] args) {
     Printer printer = new Printer("测试打印文本");
     // 运行时错误 抛出ClassCastException
     Integer o = (Integer) printer.getPrintText();
    }
}

在main方法中代码调用printer类,获取printText进行了强制转换编译可以通过但是运行就会报错,抛出ClassCastException错误。
为了防止这种情况出现,我们就可以对printer类进行改造。

public class Printer<T> {
    /**
     * 打印文字
     */
    T printText;

    public Printer(T printText) {
        this.printText = printText;
    }

    public T getPrintText() {
        return printText;
    }

    public void setPrintText(T printText) {
        this.printText = printText;
    }

    public static void main(String[] args) {
        Printer<String> printer=new Printer<String>("这是测试打印文本");
        Integer o = (Integer) printer.getPrintText();// 编译错误
        String a = printer.getPrintText();// 合法
    }
}

就像在定义方法的时候可以声明一些方法参数,在定义类的时候我们可以通过< T >的形式类声明类型参数,在类主题中可以直接引用 T。

这种带有类型参数的类就被称为泛型类。

上面的printer类就是一个泛型类,他有一个类型参数T,而在main方法中初始化printer类的时候指定的T为String,所以在之后的get方法中编译器会知道返回值为String,所以使用Integer接收的时候会编译错误。而使用String接收的时候就是合法的。

泛型类格式:

修饰词 class 类名 <T> {...}

如:

public class printer <T> {
T content;
}

public class people <J> {
J name;
}

这里的T只是习惯叫法而已。如果你乐意也可以用其他字母代替 比如 B、Y、H。都是可以的。

一个泛型类也可以有多个类型参数,多个类型参数放在一个<>中使用‘,’隔开,例子如下:


public class Printer<T,K> {
    
    Map<T,K> map;

    public Map<T, K> getMap() {
        return map;
    }

    public void setMap(Map<T, K> map) {
        this.map = map;
    }

    public static void main(String[] args) {
        Printer<String,Integer> printer=new Printer<String, Integer>();
        printer.getMap().put("key",1);// 合法
        printer.getMap().put(1,"key");// 编译错误
    }
}

泛型接口与泛型类的用法基本一致

泛型接口的格式

修饰词 interface 接口名称 <T> {...}

这里我们就举一个例子看下:

public interface people <T> {
    T setName(T name);
}

4、泛型方法

在一个方法中,如果方法的参数或者返回值中带有< T >形式的类型参数,那么这个方法称为泛型方法。在普通的类或者泛型类中都可以创建泛型方法。

泛型方法结构

// 返回值 、参数 、 方法体 都可以引用 K类型参数
修饰词 <K> 返回值 方法名称(参数) {方法体}

例子如下


public class Printer<T> {

    T printText;
    
    // 不是泛型方 T只是引用了类的类型变量而已。并没有自己定义
    public T getPrintText() {
        return printText;
    }
    // 是泛型方法 自己定义了类型变量K
    public <K> K print(K text) {
        System.out.println("打印文本:" + text);
        return text;
    }

    public static void main(String[] args) {

        Printer printer=new Printer();
        printer.print("测试打印文本");
    }
}

例子中的getPrintText()方法虽然有T但是并不是泛型方 T只是引用了类的类型变量而已。并没有自己定义。
而print()方法是泛型方法因为自己定义了类型变量K。

5、泛型数组

泛型数组是指数组的类型是‘T’的数组,如‘T[]’;

例子

public class Printer<T> {
    // 泛型数组
    public T[] content;

    public T[] getContent() {
        return content;
    }

    public void setContent(T[] content) {
        this.content = content;
    }

    public static void main(String[] args) {
        Printer<String> printer = new Printer<String>();
        printer.setContent(new String[]{"元素1", "元素2"});
        System.out.println(printer.getContent()[0]);
    }
}

注意:在泛型类中不能用泛型数组来创建数组实例。

public class Printer<T> {
    // 泛型数组
    public T[] content = new T[10]; //编译出错,不能用泛型数组来创建数组实例。
}

5、extends关键词

在定义泛型的时候,我们可以使用extends关键字来限定类型参数,语法格式为

<T extends 类名>/<T extends 接口名>
比如我们刚开始的例子中,我们想要放入箱子的食物都是甜品,那么我们就可以写 <T extends 甜品>

例子:

public class Printer<T extends Number> {

    public T content;

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }

    public static void main(String[] args) {
        Printer<Integer> printer = new Printer<Integer>(); // 合法;Integer是Number子类
        Printer<Double> printer1 = new Printer<Double>();  // 合法; Double是Number子类
        Printer<String> printer2 = new Printer<String>();  // 编译错误;String不是Number子类
    }
}

在上面的例子中,我们在print类中定义了一个 <T extends Number>,代表引入的参数类型必须是Number的子类。
根据下图我们知道 Integer 和 Double都是Number的子类,所以合法,而String并不是Number的子类,所以会出现编译错误。
在这里插入图片描述

6、"?" 通配符

‘?’是表示一个不确定的类型。由于不是像‘T’一样是一个确定的类型,所以‘?’无法用于定义变量或者类。一般用于集合中。

‘?’可以使用有上限和下限对类型进行约束

‘?’的上限‘<? extends 类名/接口>’

List<? extends Number> list = new ArrayList<>();

‘?’的下限‘<? super 类名/接口>’

List<? super Number> list = new ArrayList<>();

关于通配符还有很多东西可以讲,比如List<? extends Number> 无法add除了null只为的元素,这个我之后准备专门开一篇讲解,如果有想要知道的同学可以点个关注。

7、注意事项

1、泛型只在编译期间有效,在编译之后的字节码文件中就会被清除。
所以以下两个list对象是相等的

List<Srting> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();

System.out.println(list1.getClass()==list2.getClass()) // 打印true

因为泛型在编译之后,所以编译器也是不允许在一个类定义两个同名的方法,参数分别是List<T>List<K>

public class Printer<T,K> {
   public void print(List<T> list){
       
   }
   // 编译错误,非法的方法重载
   public void print(List<K> list2){
       
   }
}

2、不可以对泛型进行强制转换,这样存在安全隐患,会导致抛出ClassCastException异常。

 		Collection list1 = new ArrayList<Integer>();
        list1.add(1);
        List<String> list2 = new ArrayList<String>();
        list2 = (ArrayList<String>) list1;
        for (String s:list2){
            System.out.println(s); // 抛出异常ClassCastException
        }

3、不能对泛型进行instanceof操作。

   public void print(List<K> list){
       // 编译错误
       if(list instanceof Collection<String>){
           
       }
   }

8、参考

8、结语

本片文章主要讲了泛型的使用,我们知道了泛型主要是为了是编译器在编译的时候能够判断我们的参数是否正确,从而避免运行时错误。并且可以简化代码编写,无需进行多余的强制转换。
然后我们学习了如何定义 泛型类、泛型方法、泛型接口 等。
如果文章有什么错误后者同学们有疑问的地方,欢迎评论留言,
如果您喜欢,欢迎关注、点赞 谢谢🙏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章