开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情
1.什么是泛型以及泛型的好处
泛型是jdk5引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
1.1 没有用泛型约束会怎样
在这里可能有同学不是很清楚什么叫编译阶段约束操作的数据类型,接下来给大家演示下使用ArrayList集合时,不使用泛型来进行约束可能会出现什么后果。
我们可以发现,如果集合没有指定数据类型的话,这个集合可以添加任意数据类型的数据,没有任何报错,甚至你还可以通过迭代器将这个集合里面的元素遍历出来。但是一旦你想要使用过这个集合里元素的特有行为时,你会发现报错了,如上个图所示。我们不能使用length方法,因为添加到这个集合的元素里面有两个是非String类型的元素。
1.2 使用泛型的好处
如下图所示,只要我们采用泛型,在创建集合的时候,就指定泛型的对象,比如例子中我们指定了ArrayList的泛型是String类型。如果你往集合里添加非String类型的元素的话,在编译阶段就会报错。所以泛型可以在添加数据的时候,将数据类型进行统一。这也是为什么说泛型可以在编译阶段约束操作的数据类型。
2.怎样定义泛型
前面我们看了在java里,集合是怎么使用泛型的,那我们自己可不可以也来定义泛型。答案是可以。我们可以在自己定义的类上指定泛型,可以在方法上指定泛型,还可以在接口上定义泛型。
2.1 泛型类
我们在什么时候需要在类上定义泛型呢?当一个类中的数据类型不确定时,就可以创建带有泛型的类。
例子:
我们发现,在类上定义泛型时,只需在类名后面加上<类型>即可。例子中的类型我们用的是E。其实不一定得是E,你写成T、K、V等都行。别轻易写?,因为这个是通配符,可以看后文介绍。下面例子的E可以理解为变量,但不是用来保存数据的变量,而是记录数据类型的变量,你传入什么值,E就是什么值。
//格式
修饰符 class 类名<类型>{
}
//举例 创建该类对象的时候,E就能确定类型
public class ArrayList<E>{
}
实践:我们自己定义一个集合,并且使用这个集合。
当我们开始使用我们自己定义的集合时
上面的例子告诉我们,如果在类上定义了泛型,那么这个泛型可以在这个类里面通用。你可以将在这个在类上定义的泛型用到方法的形参上,也可以用到方法的返回值上。
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("阿伟");
}
}
2.2 泛型接口
//格式
修饰符 interface 接口名<类型>{
}
//举例
public interface List<E>{
}
我们重点需要知道的是如何使用一个带泛型的接口?方式有二:1.实现类给出具体类型;2.实现类延续泛型,创建对象时再确定
2.2.1 实现类给出具体类型
我们看下List接口的样子
接着创建一个类,实现List接口,并且在实现的时候给出具体类型。这里的给出具体类型是指,在实现接口的时候,给出这个接口具体的类型,效果如下:我们指定的是String类型,所以实现类对应的泛型位置就是String类型。
你可以直接使用实现类,不用再去指定泛型的类型了。
2.2.2 实现类延续泛型,创建实现类对象的时候再确定类型
我们创建了一个实现类去实现List接口,但是不指定泛型类型。这里有个点需要注意下,我们发现List接口指定发泛型是用E来表示的,你的实现类指定泛型类型的时候也要用E
使用实现类的时候再确定泛型的具体类型
3.泛型的继承和通配符
泛型是不具备继承性的,但是数据具备继承性
3.1泛型的继承
泛型是不具备继承性
数据具备继承性
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;
}
}