数据结构初阶:Java泛型
1.什么是泛型
泛型:就是接受许多类型;通俗来说就是将类型参数化,你想传什么类型的值都行。
下面详细讲:
2.引出泛型
目标:实现一个类,类中有一个能存放任何数据类型的数组,并且成员方法可以返回对应下标的值。
数组是将相同类型的值聚在一起:int[] array = new int[5]; String[] str = new String[10];
这些数组类型都定死了,只要传其他类型的值就报错。
这时我们想到类都继承于Object,写一个Object类型的数组就可以接收所有类型的值,如:

看到这里,我们意识到一个问题。我不知道要用什么类型来向下转型呀!这确实可以接收各种类型,但是这也太杂了;一个数组里有各种基本类型、各种引用类型,我们能否指定类对象只能接收哪个(哪些)类呢?——》Java就设计了泛型这一概念!!!
3.语法
class 泛型类名称<类型形参列表> {
//这里可以使用类型参数
}class ClassName<>{}class 泛型类名称<类型形参列表> extends 继承类/*这里可以使用类型参数*/{
//这里可以使用类型参数
}class ClassName<T1,T2,...,Tn> extends ParentClass<T1>{}
上述图片代码改写如下:
class MyArray<T>{Object[] objects = new Object[10];public T getElement(int index) {return (T)objects[index];}public void setElement(int index,T val){objects[index]=val;}
}代码解释:
1. 类名后的 <T> 代表占位符,表示当前类是一个泛型类。(实际上可以使用任意大写字母)了解: 【规范】类型形参常用的名称有:E 表示 ElementK 表示 KeyV 表示 ValueN 表示 NumberT 表示 TypeS, U, V 等等 - 第二、第三、第四个类型
3.1泛型类的使用
- 定义泛型类的引用:泛型类<类型实参> 变量名 ;
- 实例化泛型类对象:new 泛型类<类型实参>(构造方法实参); //这里的类型实参不写也行,因为系统会根据引用的类型实参进行推导。
示例:
MyArray<Integer> list = new MyArray<Integer>();//泛型类型不能是基本类型!!!
3.2泛型类引用的类型
先看下面的实操了解代码:
Test2.main()中的myArray是什么类型的?
因为MyArray继承了Object类,所以我们在打印时,会调用父类的toString()方法;
该方法返回调用引用的类型;
结论:可以看出myArray的类型还是MyArray,不是MyArray<Integer>!!!
小结:
1. 泛型是将类型参数化,进行传递2. 使用 <T> 表示当前类是一个泛型类。3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
4.实操

既然对类对象指定了输入数据的类型,那是否还能输入其他类型的数据?
答案是不能!!
![]()
5.擦除机制
泛型到底是怎样进行编译的呢?
其实,泛型是编译时期的一种机制,在运行期间是没有泛型这一概念的。
下图就很直观:

有同学就要问了:那泛型有什么用呢?
答:编译时自动进行类型检查和转换。
6.泛型的上界
在定义泛型类时,有时需要对传入的类型进行约束。
6.1语法
class 泛型类名称<类型形参 extends 类型边界> {......}
例:
public class ClassName<E extends Number> {//......
}代码解读:只有类型是Number或者Number的子类才能作为E的类型形参。(最多只能是Number类,这就是泛型的上界)
例:

代码解读:String不是Number的子类,所以当将String作为泛型实参时就会报错!!
7.泛型进阶
7.1通配符
class Message<T>{private T message ;public T getMessage() {return message;}public void setMessage(T message) {this.message = message;}
}public class TestDemo2 {public static void main(String[] args) {Message<String> message = new Message<>() ;message.setMessage("不上早八!");func(message);}public static void func(Message<String> temp){System.out.println(temp.getMessage());}
}
上面的代码问题在于func(),这个方法写死了,当我泛型类型不是String,而是其它就会出问题,图例:

所以我想func()什么泛型类型都能接受,就用到通配符(?)。代码改成如下:
public class TestDemo2 {public static void main(String[] args) {Message<String> message = new Message<>() ;message.setMessage("不上早八!");func(message);Message<Integer> message2 = new Message<>() ;message2.setMessage(123);func(message2); //不报错}public static void func(Message<?> temp){//什么泛型类型都能接受了System.out.println(temp.getMessage());}
}
7.1.1通配符和<E>的区别
通配符是可以在泛型类之外使用的,但E等大写字母,必须在泛型中使用。(<大写字母>是泛型标识)
7.2通配符的上界
语法:
<? extends 上界>
<? extends Number> //解读:只能接收Number或者是Number的子类类型7.2.2实操
将用下面的关系做示例

示例1:
class Biology {
}
class Animal extends Biology {
}
class Dog extends Animal {
}
class Cat extends Animal {
}class Message<T> { // 设置泛型private T message ;public T getMessage() {//获取return message;}public void setMessage(T message) {//设置this.message = message;}
}
public class TestDemo {public static void main(String[] args) {Message<Dog> message = new Message<>() ;message.setMessage(new Dog());fun(message);Message<Cat> message2 = new Message<>() ;message2.setMessage(new Cat());fun(message2);}public static void fun(Message<? extends Animal> message){System.out.println(message.getMessage());message.getMessage();}
}注意1:
通配符可不像<E>,你不能写成 ? element = get....(); 所以你不知道传入了什么泛型类型

注意2:

所以我们在传入时要停下来想想了。
总结:通配符的上界,不能进行写入数据,只能进行读取数据。
7.3通配符的下界
语法:
<? super 下界>
<? super Number> //解读:只接收Number或者Number的父类
示例·:
通配符的下界与上界截然相反:
public static void main(String[] args) {Message<Animal> message = new Message<>() ;message.setMessage(new Animal());fun(message);Message<Biology> message2 = new Message<>() ;message2.setMessage(new Biology());fun(message2);}public static void fun(Message<? super Animal> message){//只接受Animal或者它的父类System.out.println(message.getMessage());message.setMessage(new Dog());//此时可以修改(传入数据),因为Dog、Cat都是Animal子类message.setMessage(new Cat());}
这放随便放,但取就要小心了!!!
7.3总结
总结:通配符上界取随便取,放值要思考;通配符下界放随便放,但取就要注意。


