什么是泛型:
在了解一个技术点的时候最有效的办法就是先看看官方文档解释,OK,官网给出的泛型定义:
泛型是 Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
简单来说:泛型就是“参数化类型”。众所周知参数分为“形参、实参”,这里的”参数化类型”亦是如此,在定义泛型的时候就是“形参”,调用时传入具体的参数即“实参”。
泛型出现在哪:
好多朋友就问了,在哪会出现泛型?在平时的撸码中,咱们最经常接触的泛型就是集合了(Collection),咱们先来看看现有的代码。
这里以 ArrayList 举例。
接着看一下 ArrayList 的类
图中红色款内有一个E,这个就是泛型 (形参)。在创建的ArrayList的时候,你可以给出任何类型:基本数据类、引用数据类型。它可以接受任何传入的具体的类型实参,并且在该类或接口中的所有E均为该传入的具体类型。我们常见还有T、E、K、V等形式的参数都是表示泛型形参。
为何使用泛型:
ITEM-1:
编码时的约束。如下图,我们的 ArrayList 给的类型为 String ,在我们进行 add 操作时,如果添加的元素不是 String 类型时,你的IDEA 就会给你一个红杠杠,不能进行编译,因为使用了泛型,就是直接给出了限定类型。
ITEM-2:
在撸码的时候,ArrayList也可以不给出具体的类型,这个没问题,但是从ArrayList取元素的时候该元素就为 Object 类型了,这个时候需要进行强转,这样就很容易出现 类型转换异常/ java.lang.ClassCastException,使用泛型既可以尽量避免这样的异常,同时也无需强转,取值时IDEA已经能够确认该值的类型。
ITEM-3:
减少类的开发,增强类的通用性。我们在开发中,会出现很多的实体类,这个时候有一些公用的方法需要整理,需要根据不同的类来进行相同的操作(类似Base Service),这个时候如果我们每个实体类都创建一个这样的公共类,这样的工作量是庞大的,而泛型就恰好帮我们解决了这样的问题。
怎么使用泛型:
长篇大论的东西都了解完之后,咱们就真枪实弹的运用一下。用一个经典的例子:学校、老师、学生。在学校里的人既有学生还有老师。
/**
* 创建一个 student 的类型
*/
public class Student {
private String name;
private String age;
... // 省略 get set 方法
}
/**
* 创建一个 teacher 的类型
*/
public class Teacher {
private String name;
private Integer age;
... // 省略 get set 方法
}
/**
* 创建一个 school 的类型,并且给 school 一个 泛型
* 在该类里所有用到 E 的地方都表示调用时传入的具体参数类型
* @param <E>
*/
public class School<E> {
private E person;
public E getPerson() {
return person;
}
public void setPerson(E person) {
this.person = person;
}
}
接下来就是写一个测试类,为了不让代码看花眼,咱们一个一个来测试。首先咱们先来测试学生类
public class SchoolDemo {
public static void main(String[] args) {
// 创建一个学生对象
Student student = new Student();
student.setName("张三同学");
student.setAge(15);
// 创建学校对象,将 Student 类型放入
School<Student> school_stu = new School<>();
// 将 Student 对象放入 School
school_stu.setPerson(student);
// 这里我们调用 get 方法就可以直接获取到对应传入的具体对象类型
Student person_stu = school_stu.getPerson();
// 打印一下内容
System.out.println(person_stu.toString());
}
}
打印出来的结果:
接着咱们一样的套路,一样的方法,来测试一下教师类
public class SchoolDemo {
public static void main(String[] args) {
// 创建一个老师对象
Teacher teacher = new Teacher();
teacher.setName("王美丽老师");
teacher.setAge(25);
// 创建学校对象,将 Teacher 类型放入
School<Teacher> school_tea = new School<>();
// 将 Teacher 对象放入 School
school_tea.setPerson(teacher);
// 这里我们调用 get 方法就可以直接获取到对应传入的具体对象类型
Teacher person_tea = school_tea.getPerson();
// 打印一下内容
System.out.println(person_tea.toString());
}
}
打印出来的结果:
这样就不用创建多个 School 类来去操作这两各类了,我们只不过在创建 School 的时候放入的具体参数不同,set、get方法也会约束你的参数类型,减少出错率,方便取值。
关于生成的实例类型:
上面的例子,我们就会思考,在创建 School 的时候,我们传入的是不同的实力参数,那么生成的 School 是否一样的呐?
答案是:一样的
public class SchoolDemo {
public static void main(String[] args) {
// 创建一个学生对象
Student student = new Student();
School<Student> school_stu = new School<>(student);
// 创建一个老师对象
Teacher teacher = new Teacher();
School<Teacher> school_tea = new School<>(teacher);
// 我们打印一下 类,看看是否为true
System.out.println("school_stu Class:" + school_stu.getClass());
System.out.println("school_tea Class:" + school_tea.getClass());
System.out.println(school_stu.getClass() == school_tea.getClass());
}
}
打印的结果:
通过上面的例子,我们虽然传入了不同的泛型实参,但并不没有真正的生成不同的类型,也就是说我们传入不同的参数实参,但在内存上却只存在一个,就是原来的基本类型(这里为:School)。但我们在逻辑上是可理解为多个不同的类型。
这是因为,Java提出泛型概念的时候,只不过是作用在编码阶段,一旦进入编译阶段后,会先对其泛型进行校验,完成校验之后会将泛型的相关信息抹掉,成功编译后的 class 文件中是不包含任何泛型信息的,更不会进入运行阶段了。
泛型类型在逻辑上我们可以看为不同的多个类型,在实际运行时,都是相同的基本类型
类型通配符:
泛型的基本用法咱们 Get 好了之后,来看看进阶用法。上面的 Student、Teacher我们可以给他们一个父类(Person),同时让其继承这个父类,我们来改造一下代码
/**
* 创建父类 Person
*/
public class Person {
private String name;
private Integer age;
... // 省略 get set 方法
}
/**
* 创建一个 student 的类型,继承 Person
*/
public class Student extends Person{
}
/**
* 创建一个 teacher 的类型,继承 Person
*/
public class Teacher extends Person{
}
重点在测试类里面:
public class SchoolDemo {
public static void main(String[] args) {
// 创建一个学生对象
Student student = new Student();
School<Student> school_stu = new School<>(student);
getPerson(school_stu); // 1
// 创建一个老师对象
Teacher teacher = new Teacher();
School<Teacher> school_tea = new School<>(teacher);
getPerson(school_tea); // 2
}
// 我们在这里添加一个 getPerson 方法,参数为 School<Person>
public static void getPerson(School<Person> person){
System.out.println(person.getPerson().toString());
}
}
上面这段代码分别会在1、2两处报红,不会让你编译的。这就奇怪了,我们都继承了Person类,为什么还不能编译通过?我们想一下:这里我们参数虽然是父类,但我们在 getPerson 的时候,返回的是 Student?还是 Teacher,而且在编译过程中顺序是不可控的,在必要的时候必须进行类型判断,且进行强转,这就与泛型的理念相背离了。因此在逻辑上我们并不能将 Person 看为 Student、Teacher 的父类
这个时候我们就需要借助通配符来让其在逻辑上可以来表示为一个父类的引用类型,类型通配符也就因此而生。
类型通配符我们基本使用问号(?)来代替具体的类型实参。值得注意的是,这里的 ?是类型实参,而非形参!逻辑上就是所有 School<实参>的父类,一起来看看代码
public class SchoolDemo {
public static void main(String[] args) {
// 创建一个学生对象
Student student = new Student();
School<Student> school_stu = new School<>(student);
getPerson(school_stu); // 1
// 创建一个老师对象
Teacher teacher = new Teacher();
School<Teacher> school_tea = new School<>(teacher);
getPerson(school_tea); // 2
// 我们在学校类里面 放一个 String类型
School<String> school_str = new School(new String());
getPerson(school_str); // 3
}
// 我们在这里添加一个 getPerson 方法,参数为 School<?>
public static void getPerson(School<?> person){
System.out.println(person.getPerson().toString());
}
}
这样1、2就可以进行编译了,新的问题又出现了,我在3的这个位置放入的是一个 String 类型的学校。编译器是不会编译出错的,因为我们使用的是通配符,这时我们想对参数进行一定的限制,必须为 Person的子类的时候怎么处理?
// 我们在这里添加一个 getPerson 方法,参数为 School<? extends Person>
public static void getPerson(School<? extends Person> person){
System.out.println(person.getPerson().toString());
}
使用类型通配符上线就解决啦,我们让通配符去继承某个父类,这样就可以对参数进行一定的约束了。
在平时开发中,合理的运用泛型会给我们的开发带了便利和代码量的减少。~~OK,以上就是对泛型相关的知识了,如有不对请指出,多多海涵。