目录
1 为什么使用泛型
2 泛型的语法
3.泛型的编译步骤
3.1 擦除机制
3.2 不可以实例化泛型类型数组
4.了解裸类型
5.泛型的上界
6.泛型方法
7.通配符(?)
7.1 理解通配符
7.2 通配符上界
7.3 通配符下界
8.包装类
8.1基本数据类型对应包装类
8.2 装箱和拆箱
1 为什么使用泛型
普通的类和方法,只能使用具体的类型,比如基本类型或者自定义的类,如果要应用多种类型的代码,就非常的不方便。
而从JDK1.5后,引入了泛型这个概念,泛型和函数的区别就是
函数传参传的是值,而泛型传的是类型,这样泛型就适用于许多许多类型,也就是将类型当做了参数
2 泛型的语法
⚜️在写泛型语法之前,我们先思考一下,
如何实现一个类,这个类中包含一个数组成员,使得这个数组可以存放任何类型的数据,并且这个也可以根据成员方法返回数组中某个元素下标值。
简单分析一下吧
正常的数组是只能存放指定类型元素的值,但我们学过了,所有类的父类,默认都是Object类型的,那么数组可以为Object吗,下面我们来浅浅的试一下
创建一个类
注意看,我们将数组类型设置成Object后没有报错,说明是可行的
然后我们开始存放数据,并且获取下标元素的值
这里我们可以,强制类型转化一下
虽然说,这样数组任何类型也可以存放,但是感觉不太方便,而且条理比较混乱,
所以还是想让他只有一种数据类型,此时,我们就一个是考虑泛型了,
泛型存在的意义,就是指定当前的容器,然后想要什么类型的对象,让编译器去检查,把想要的类型,当做参数去传递。
下面直接上语法吧
class 泛型类名称<类型形参列表> {
//这里可以使用类型参数
}
class ClassName<T1,T2...,Tn>{
}
class 泛型类名称<类型形参列表> extends 继承类/*这里可以是类型参数*/ {
//这里可以使用类型参数
}
class ClassName<T1,T2...,Tn> extends ParentClass<T1> {
//这里可以使用部分类型参数
}
然后,现在把刚刚那个问题代码修改一下,引入泛型试试看效果
先写一个泛型类
//<T>代表当前类是泛型类
class MyArray2<T> {
public T[] array = (T[])new Object[10];
public T getPos(int pos) {
return array[pos];
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
}
下面我们在<>中指定类型,此时就只能存放这个数据类型的数据了
public static void main(String[] args) {
//<>中指定类型,此时这个类里面,只能放这个数据类型的数据
MyArray2<String> myArray = new MyArray2<String>();
myArray.setVal(0,"nb");
myArray.setVal(1,"xawl");
String s = myArray.getPos(1);
String s1 = myArray.getPos(0);
System.out.println(s);
System.out.println(s1);
}
我们比如在定义一个Integer类型的
MyArray2<Integer> myArray2 = new MyArray2<Integer>();
myArray2.setVal(0,12);
myArray2.setVal(1,13);
Integer I = myArray2.getPos(0);
System.out.println(I);
例子就写到这里
下面说一下泛型类语法
泛型类<类型实参> 变量名;//定义一个泛型类引用
new 泛型类<类型实参> (构造方法实参);//实例化一个泛型类对象
注意事项:
(1)类名后的<T>叫占位符,意思就是当前的类是泛型类
(2)不需要进行强制类型转化
(3)Java中,不可以new泛型类型的数组
(4)注意<>中必须要引用类型
否则,就会报错
(5)泛型类使用中可以省略类型实参的填写
MyArray<Integer> list = new MyArray<>();
这是因为,编译器可以推导出实例化需要的类型实参为Integer
3.泛型的编译步骤
3.1 擦除机制
擦除机制就是,在编译的过程中,将泛型T替换为Object
并且擦除机制就是编译时期的一种机制,运行期间没有泛型这个概念
3.2 不可以实例化泛型类型数组
思考这样一个例子
既然所有的T都替换为Object,那为什么这样就不可以写
下面看一下这个例子你就明白了
⚜️ 那为什么不能被转化呢?
很简单Object数组中存在很多的类型,然后此时你用Integer来转化很多种类型,那肯定是不行的,编译器从安全考虑不会让你通过的,
所以我们正确的应该是这样做
package Demo01;
import java.lang.reflect.Array;
import java.util.Arrays;
class MyArray<T> {
public T[] array;
public MyArray(Class<T> clazz, int capacity) {
array = (T[]) Array.newInstance(clazz,capacity);
}
public T[] getArray() {
return array;
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
public T getPos(int pos) {
return this.array[pos];
}
}
public class Test01 {
public static void main(String[] args) {//指定数组类型是Integer
MyArray<Integer> myArray = new MyArray<>(Integer.class,10);
myArray.setVal(0,10);
Integer[] tmp = myArray.getArray();
System.out.println(Arrays.toString(tmp));
}
}
4.了解裸类型
下面看一下这个
我们写了一个泛型类,但是并没有带参数类型,而且也没报错,那么我们把这个叫做裸类型
这里说明裸类型,是为了兼容老版本的API保留机制,我们不要自己去使用裸类型
5.泛型的上界
语法格式
class 泛型类名称<类型形参 extends 类型边界> {
......
}
下面思考一下,如果要写一个泛型类,找数组中的最大值,应该怎么做
首先,肯定是直接比较不了的,因为T是引用数据类型,引用类型比较要用比较器
所以,
这里也可以看到Comparable也是个泛型接口
所以泛型类可以这样写
class Alg<T extends Comparable<T>> {
public T findMaxVal(T[] array) {
T maxVal = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(maxVal) > 0) {
maxVal = array[i];
}
}
return maxVal;
}
}
6.泛型方法
语法格式
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
直接上例子
在前面我们找数组中的最大值,是在泛型类中写了一个方法,来实现的
要调用这个方法,就必须要有个对象,如果不想有这个对象,那就可以把这个方法变成静态的就可以了,所以还有一种方法是
⚜️ 可以把这个方法写成静态的,然后放在普通类中
class Alg2 {
//静态方法
public static<T extends Comparable<T>> T findMaxVal(T[] array) {
T maxVal = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(maxVal) > 0) {
maxVal = array[i];
}
}
return maxVal;
}
}
⚜️ 还有个方法是,继续写一个泛型方法,只不过这个方法也是成员方法
class Alg3 {
//泛型方法:成员方法
public <T extends Comparable<T>> T findMaxVal2(T[] array) {
T maxVal = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(maxVal) > 0) {
maxVal = array[i];
}
}
return maxVal;
}
}
因为这个不是静态方法,所以还是要引用对象,
public static void main(String[] args) {
Alg3 alg3 = new Alg3();
Integer[] array = {100,200,24,34,55};
int val = alg3.findMaxVal2(array);
System.out.println(val);
}
7.通配符(?)
7.1 理解通配符
通配符是用来解决泛型无法协变的问题的
协变指的是如果child是parent的子类,那么List<Child>也应该是List<Parent>的子类。但是泛型是不支持这样的父子类关系的。
这是因为
class MyArray<T> {
public T[] array = (T[]) new Object[10];
public T getPos(int pos) {
return this.array[pos];
}
public void setval(int pos,T val) {
this.array[pos] = val;
}
public T[] getArray() {
return array;
}
}
public class Test01 {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<>();
System.out.println(myArray);
MyArray<String> myArray2 = new MyArray<>();
System.out.println(myArray2);
}
}
🤠 可以看到<>尖括号中的内容,不参与类型的组成。
所以在泛型中List<Child>不是List<Parent>的子类
下面看一个例子
class Message<T> {
private T message ;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class Test02 {
public static void main(String[] args) {
Message<String> message = new Message() ;
message.setMessage("xawl");
fun(message);
}
public static void fun(Message<String> temp){
System.out.println(temp.getMessage());
}
}
⚜️ 如果说此时泛型的类型不是String而是Integer
🤠 所以这里就希望的是可以接收所有的泛型类型,但是又不能够让用户随意修改。这样就需要使用通配符‘’?‘’来解决
public static void fun(Message<?> temp){
System.out.println(temp.getMessage());
}
所以通配符“?”表示,可以接收任意类型
在泛型中只有上界,没有下界
而在通配符?的基础上又产生了两个子通配符:
?extends类:设置通配符上界
?super类:设置通配符下界
7.2 通配符上界
语法:
Vector<? extends 类型1> x = new Vector<类型2>();
类型1指定一个数据类型,那么类型2就只能是类型1或者是类型1的子类:
Vector<? extends Number> x = new Vector<Integer>();
下面看一个例子
class Food {}
class Fruit extends Food {}
class Apple extends Fruit {}
class Banana extends Fruit {}
class Test<T> { // 设置泛型上限
private T val ;
public T getVal() {
return val;
}
public void setVal(T Val) {
this.val = val;
}
}
public class Test03 {
public static void main(String[] args) {
Test<Apple> test = new Test<>() ;
test.setVal(new Apple());
fun(test);
Test<Banana> test2 = new Test<>() ;
test2.setVal(new Banana());
fun(test2);
}
//只要是Fruit或者Fruit的子类即可
public static void fun(Test<? extends Fruit> temp){
System.out.println(temp.getVal());
}
}
⚜️ 如果此时在fun函数中对temp添加元素,就会报错
这是因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子类无法确定,所以添加元素就会报错,所以只能获取元素
总结:通配符的上界,不能进行写入数据,只能进行读取数据。
7.3 通配符下界
语法:
Vector<? super 类型1> x = new Vector<类型2>();
类型1指定一个数据类型,那么类型2就只能是类型1或者是类型1的父类:
Vector<? super Integer> x = new Vector<Number>();
下面看一个例子:
class Food {}
class Fruit extends Food {}
class Apple extends Fruit {}
class Banana extends Fruit {}
class Test<T> { // 设置泛型上限
private T val ;
public T getVal() {
return val;
}
public void setVal(T Val) {
this.val = val;
}
}
public class Test03 {
public static void main(String[] args) {
Test<Fruit> test = new Test<>() ;
test.setVal(new Fruit());
fun2(test);
Test<Food> test2 = new Test<>() ;
test2.setVal(new Food());
fun2(test2);
}
public static void fun2(Test<? super Fruit> temp){
System.out.println(temp.getVal());
}
⚜️ 如果此时在fun2函数中对temp添加元素,还会报错吗
public static void fun2(Test<? super Fruit> temp){
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
temp.setVal(new Apple());//这个是Fruit的子类
temp.setVal(new Fruit());//这个是Fruit的本身
// Fruit fruit = temp.getMessage(); 不能接收,这里无法确定是哪个父类
System.out.println(temp.getVal());//只能直接输出
}
总结:通配符的下界,不能进行读取数据,只能进行写入数据。
8.包装类
8.1基本数据类型对应包装类
在java中,因为基本类型不是继承Object,为了在泛型代码中可以支持基本类型,java给每个基本类型都搞了一个包装类型。
基本数据类型 |
包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
8.2 装箱和拆箱
装箱:
public static void main(String[] args) {
int a = 20;
Integer integer = a;//自动装箱
Integer integer2 = new Integer(a);//显示装箱
Integer integer3 = Integer.valueOf(a);//显示装箱
System.out.println(integer);
System.out.println(integer2);
System.out.println(integer3);
}
⚜️ 下面看一下,自动装箱的字节码文件
拆箱:
public static void main(String[] args) {
int a = 30;
Integer integer = a;
int val = integer;//自动拆箱
System.out.println(val);
int val2 = integer.intValue();//显示拆箱
System.out.println(val2);
double val3 = integer.doubleValue();//显示拆箱
System.out.println(val3);
}
⚜️ 下面看一下,自动拆箱的字节码文件