☕目录☕
前言
思维导图
一、什么是泛型
二、引出泛型
2.1 泛型的语法
三、泛型类的使用
3.1 语法
3.2 示例
3.3 类型推导
四、裸类型(Raw Type)(了解)
五、泛型是如何编译的
六、泛型的上界
6.1 语法
6.2 示例
七、泛型方法
写在最后
现在我们就来学习 Java数据结构 的第二个前置知识点 —— 泛型;
现在,铁汁们就可以端起你们的小板凳,听我细细道来......
下面,正文开始......
一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类;如果要编写可以应用多种类型的代码,这种刻板的限制对代码的束缚就会很大。
—— 来源《Java编程思想》对泛型的介绍
泛型是在JDK1.5引入的新的语法;
通俗讲,泛型:就是广泛的应用于多种类型;从代码上讲,就是对类型实现了参数化。
即:类型作为参数进行传递。
第一个问题:实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据;
解答一:
实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据:
class MyArray {
public int[] array = new int[10];
}
//这个不可以,整形数组 只能存放 整型类型的数据
但是我们知道,Object类 是任何类型的父类,那么 我们是不是可以把 整形数组 换成 Object类型的数组呢?——答案是可以的:
class MyArray {
public Object[] array = new Object[10];
}
第二个问题:在第一个问题的基础上,需要根据成员方法返回数组中某个下标的值:
解答二:
根据成员方法返回数组中某个下标的值:
//获取 pos下标的值
public Object getPos(int pos) {
return array[pos];
}
//给 pos下标放一个元素
public void setPos(int pos,Object val) {
array[pos] = val;
}
那么,我们就可以这样去调用:
可是,这样的话就写的不好 —— 既然里面什么都可以放,那么我们就控制不了它了;
比如说,想要获取下标是1的元素:
为什么会报错呢?
我们都知道1下标元素是一个字符串类型啊;
可是编译器不知道,因为上面的 getPos方法的返回值是Object类型:
这就意味着,拿出的类型 可以是任何类型的数据(即使 自己知道是 字符串类型),可是编译器不知道(它认为拿出的数据可能是 继承于Object类中的任何一类);
把 Object类型 赋给 String类型,父类 给 子类,向下转型,编译器不会编译通过;
所以说,需要给编译器一个信号 —— 通过强制类型转换 使得编译器知道所拿出的数据是String类:
所以说,这样的话就会有一个很大的缺点 —— 乱!!!!!!
上面才放了两个元素,用肉眼可以看的来;
如果放的元素很多,而且里面的元素时刻都在发生改变,
那么,就算自己知道里面的类型,那么也会特别特别复杂;
难道每一次都需要强制类型转换吗?
这明显是不可能的......
以上代码实现后,我们会发现两个问题:
而我们所希望的是:
于是,Java为了解决这个问题,就引入了泛型......
//语法一:
class 泛型类名 <类型形参列表> {
}
//类型形参列表 一般使用大写字母来表示的;
//作为形参列表 的字母 可以有一个或者多个(无论有多少个,调用的时候都需要匹配)
//语法二:用于继承
class 泛型类名 <类型形参列表> extends 继承类/*这里可以使用类型参数*/ {
}
//如:
class ClassName extends ParentClass {
}
随便插入一个:
泛型在源码中的继承:
于是,上面的代码可以这样修改:
package GenericDemo;
/**
* :占位符 ——> 代表当前类是一个泛型类
* @param
*/
class MyArray {
public T[] array = (T[])new Object[10];//这个写法也不好,但是先保证不报错
//获取 pos下标的值
public T getPos(int pos) {
return array[pos];
}
//给 pos下标放一个元素
public void setPos(int pos,T val) {
array[pos] = val;
}
}
public class TestDemo1 {
public static void main(String[] args) {
MyArray myArray = new MyArray();
myArray.setPos(0,1);
myArray.setPos(1,2);
Integer ret = myArray.getPos(0);
System.out.println(ret);
}
}
【注意】
【了解】:
【规范】类型形参一般使用一个大写字母,常用的名称有:
- E 表示 Element;
- K 表示 Key;
- V表示 Value;
- N 表示 Number;
- T 表示 Type;
- S,U,V 等等 —— 第二、第三、第四个类型。
泛型类<类型实参> 变量名 = new 泛型类<实参类型> (构造方法实参);
【注意】泛型只能接受类,所有的基本数据类型 必须使用包装类(这个在上面也提到过)。
当编译器 可以根据上下文推导出类型实参时,可以省略类型实参(这个在上面也提到过):
裸类型 是一个泛型类,但没有带类型实参;
如:MyArray 就是一个裸类型:
【注意】我们写的时候 不要写裸类型,裸类型是为了兼容 老版本的API保留机制。
但是如果指定了裸类型的时候(本身是一个泛型类,但是没有用尖括号),那么就会出现一开始的老毛病:又要去进行强制类型转换之类的操作,非常的麻烦......
小结:
从而我们得出了一个结论:
Java的泛型,是在编译的时候,擦除为了Object类型;
而这种机制,叫做:擦除机制。
那么有的人就说了,既然编译的时候,把T替换成了Object,
那么,为什么我执行以下代码的时候会报错:
原因是这样子的:
Java的数组很特殊(在堆上),在实例化数组的时候,T不是一个具体的类型;
假设可以成功,那么上面的代码就会和这个代码类似:
那么就又会出现 一开始出现的毛病:需要强制类型转换啥的......
如果有兴趣的话,可以去看一看有关擦除机制的传送门:
传送门:Java泛型擦除机制之答疑解惑
在定义泛型类时,有时需要对传入的 类型变量 做一定的约束,可以通过类型边界来约束。
class 泛型类名<类型形参 extends 类型边界> {
......
}
【了解】如果没有指定类型 边界E,那么可以视为 E extends Object 。
写一个泛型类,类中有一个方法,求学生年龄的最大值
方法的实现:
调用:
方法的实现部分 错误的原因是:
在指定数组是T类型的时候,那么 <> 里面的Integer是 引用类型;
而 引用类型 是不可以直接 用大于和小于号进行比较;
正确的方法是:可以去实现一下Comparable接口;
但是,由于 在编译期间,T类型 又被擦除为了 Object类型,而Object类型 没有Comparable方法;
所以又需要有一个上界,使用关键字extends(这个不是继承的意思):
package GenericDemo;
//写一个泛型类,类中有个方法,比较年龄的大小
class Alg> {
public T findMax (T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (max.compareTo(array[i]) < 0){
max = array[i];
}
}
return max;
}
}
class Student implements Comparable{
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
public class TestDemo2 {
public static void main(String[] args) {
Alg alg = new Alg<>();
Student[] students = new Student[3];
students[0] = new Student("a",15);
students[1] = new Student("b",25);
students[2] = new Student("c",5);
Student student = alg.findMax(students);
System.out.println(student);
}
}
注意:泛型没有所谓的下界!
上面我们实现 求学生年龄最大值 的方法中,在调用的时候,需要先创建一个对象,然后需要去通过对象的引用 来调用findMax方法;
那么,有没有一种可以不用去实例化对象,调用findMax的方法的办法呢?
——有,把方法写成 静态方法 就可以了:
改成静态方法的时候,需要在static后面加上
,否则会报错;
泛型:
只要知道了 到第二个标题的知识点,就可以去解决80%的集合的代码了;
只要知道了 到第五个标题的知识点,就可以去看懂90%的集合的代码了;
只要知道了 到第七个标题的知识点,就可以去看懂95%的集合的代码了;
剩下的5%就属于进阶了 —— 通配符......
嗯,通配符的话,现阶段可以不用做过多的要求......
......
由于博主水平有限,可能会出现一些表达不清楚,或者出现一些其他的情况,
欢迎各位铁汁们指出来,和博主一起改正,
一起努力,共同进步;
好了,如果这篇博客对铁汁们有帮助的话,可以送一个免费的 赞 嘛;
当然,顺手点个关注是再好不过的了......