Java里的泛型

98 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情

1.什么是泛型以及泛型的好处

泛型是jdk5引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。

1.1 没有用泛型约束会怎样

在这里可能有同学不是很清楚什么叫编译阶段约束操作的数据类型,接下来给大家演示下使用ArrayList集合时,不使用泛型来进行约束可能会出现什么后果。

image.png

image.png

我们可以发现,如果集合没有指定数据类型的话,这个集合可以添加任意数据类型的数据,没有任何报错,甚至你还可以通过迭代器将这个集合里面的元素遍历出来。但是一旦你想要使用过这个集合里元素的特有行为时,你会发现报错了,如上个图所示。我们不能使用length方法,因为添加到这个集合的元素里面有两个是非String类型的元素。

1.2 使用泛型的好处

如下图所示,只要我们采用泛型,在创建集合的时候,就指定泛型的对象,比如例子中我们指定了ArrayList的泛型是String类型。如果你往集合里添加非String类型的元素的话,在编译阶段就会报错。所以泛型可以在添加数据的时候,将数据类型进行统一。这也是为什么说泛型可以在编译阶段约束操作的数据类型。

image.png

2.怎样定义泛型

前面我们看了在java里,集合是怎么使用泛型的,那我们自己可不可以也来定义泛型。答案是可以。我们可以在自己定义的类上指定泛型,可以在方法上指定泛型,还可以在接口上定义泛型。

2.1 泛型类

我们在什么时候需要在类上定义泛型呢?当一个类中的数据类型不确定时,就可以创建带有泛型的类。

例子:

我们发现,在类上定义泛型时,只需在类名后面加上<类型>即可。例子中的类型我们用的是E。其实不一定得是E,你写成T、K、V等都行。别轻易写?,因为这个是通配符,可以看后文介绍。下面例子的E可以理解为变量,但不是用来保存数据的变量,而是记录数据类型的变量,你传入什么值,E就是什么值。

//格式
修饰符 class 类名<类型>{
}
//举例 创建该类对象的时候,E就能确定类型
public class ArrayList<E>{
}

实践:我们自己定义一个集合,并且使用这个集合。

image.png

当我们开始使用我们自己定义的集合时

image.png

上面的例子告诉我们,如果在类上定义了泛型,那么这个泛型可以在这个类里面通用。你可以将在这个在类上定义的泛型用到方法的形参上,也可以用到方法的返回值上。

2.2 泛型方法

如果你方法中形参的类型不确定时,你有两种解决方案。第一种是我们上面提到的在类名后面定义泛型,第二种是在方法声明上定义自己的泛型。前者定义的泛型在整个类中都可以用,后者定义的泛型只能在本方法使用

同理此处的T可以理解成记录类型的变量。不一定要写成T,你也可以写成E T等。

//格式
修饰符<类型> 返回值类型 方法名(类型 变量名){}
//举例
public<T> void show(T t){}

实践:定义一个工具类,LIstUtil。类中定义一个静态方法addAll,用来添加多个集合的元素。

public class ListUtil {

    private ListUtil(){}

    //其中定义一个静态方法addAll,用来添加多个集合的元素

    /**
     * 参数一:集合
     * 参数二:最后,要添加的元素
     */
    public static<E> void addAll(ArrayList<E> list,E e1, E e2, E e3, E e4){
        list.add(e1);
        list.add(e2);
        list.add(e3);
        list.add(e4);
    }

    public void show(){
        System.out.println("阿伟");
    }
}

image.png

2.2 泛型接口

//格式
修饰符 interface 接口名<类型>{
}
//举例
public interface List<E>{
}

我们重点需要知道的是如何使用一个带泛型的接口?方式有二:1.实现类给出具体类型;2.实现类延续泛型,创建对象时再确定

2.2.1 实现类给出具体类型

我们看下List接口的样子

image.png 接着创建一个类,实现List接口,并且在实现的时候给出具体类型。这里的给出具体类型是指,在实现接口的时候,给出这个接口具体的类型,效果如下:我们指定的是String类型,所以实现类对应的泛型位置就是String类型。

image.png 你可以直接使用实现类,不用再去指定泛型的类型了。

image.png

2.2.2 实现类延续泛型,创建实现类对象的时候再确定类型

我们创建了一个实现类去实现List接口,但是不指定泛型类型。这里有个点需要注意下,我们发现List接口指定发泛型是用E来表示的,你的实现类指定泛型类型的时候也要用E image.png 使用实现类的时候再确定泛型的具体类型

image.png

3.泛型的继承和通配符

泛型是不具备继承性的,但是数据具备继承性

3.1泛型的继承

泛型是不具备继承性 image.png 数据具备继承性 image.png

3.2泛型的通配符的使用

public class GenericsDemo6 {
    public static void main(String[] args) {
        /**
         * 需求:
         * 定义一个方法,形参是一个集合,但是集合中的数据类型不确定
         */

        //创建集合对象
        ArrayList<Ye> list1 = new ArrayList<>();
        ArrayList<Fu> list2 = new ArrayList<>();
        ArrayList<Zi> list3 = new ArrayList<>();
        ArrayList<Student2> list4 = new ArrayList<>();

        //调用method方法
        method(list1);
        method(list2);
        method(list3);//在idea里,这里会变红
        method(list4);//在idea里,这里会变红
    }

    /**
     * 此时,泛型里面写的是什么类型,那么只能传递什么类型的数据
     * 弊端:
     *  利用泛型方法有一个小弊端,此时他可以接受任意的数据类型
     *  study.Ye study.Fu study.Zi Student
     * 希望:本方法虽然不确定类型,但是以后我希望只能传递Ye study.Fu study.Zi
     *
     * 此时我们就可以使用泛型的通配符
     *      ?也表示不确定的类型
     *      他可以镜像类型的限定
     *      ?extend E:表示可以传递E或者E所有的子类型
     *      ?super E:表示可以传递E或者E所有的父类类型
     *
     * 应用场景:
     *      1.如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口
     *      2.如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型的通配符
     * 泛型的通配符
     *      关键点:可以限定类型的范围
     *
     * @param list
     */
    //public static<E> void method(ArrayList<E> list){}
    public static void method(ArrayList<? super Fu> list){

    }
}

class Ye {}
class Fu extends Ye{}
class Zi extends Fu{}
class Student2{}

4.泛型的应用场景

定义类、方法、接口的时候,如果类型不确定,就可以定义泛型。如果类型不确定,但是能知道是哪个继承体系中的,可以使用泛型的通配符。

5.类使用多个泛型

其实一个类是可以定义多个泛型的,下面我们来尝试下

public class Person<E,T> {
    
    private E name;
    private T age;

    public Person(E name, T age) {
        this.name = name;
        this.age = age;
    }

    public E getName() {
        return name;
    }

    public void setName(E name) {
        this.name = name;
    }

    public T getAge() {
        return age;
    }

    public void setAge(T age) {
        this.age = age;
    }
    
}