深圳幻海软件技术有限公司 欢迎您!

详解Java【泛型】

2023-04-14

目录1为什么使用泛型2泛型的语法3.泛型的编译步骤3.1擦除机制3.2不可以实例化泛型类型数组4.了解裸类型5.泛型的上界6.泛型方法7.通配符(?)7.1理解通配符7.2通配符上界7.3通配符下界 8.包装类8.1基本数据类型对应包装类8.2装箱和拆箱1为什么使用泛型普通的类和方法,只能

目录

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后没有报错,说明是可行的

然后我们开始存放数据,并且获取下标元素的值

 这里我们可以,强制类型转化一下

 虽然说,这样数组任何类型也可以存放,但是感觉不太方便,而且条理比较混乱,

所以还是想让他只有一种数据类型,此时,我们就一个是考虑泛型了,

泛型存在的意义,就是指定当前的容器,然后想要什么类型的对象,让编译器去检查,把想要的类型,当做参数去传递。

下面直接上语法吧

  1. class 泛型类名称<类型形参列表> {
  2. //这里可以使用类型参数
  3. }
  4. class ClassName<T1,T2...,Tn>{
  5. }
  6. class 泛型类名称<类型形参列表> extends 继承类/*这里可以是类型参数*/ {
  7. //这里可以使用类型参数
  8. }
  9. class ClassName<T1,T2...,Tn> extends ParentClass<T1> {
  10. //这里可以使用部分类型参数
  11. }

然后,现在把刚刚那个问题代码修改一下,引入泛型试试看效果

先写一个泛型类

  1. //<T>代表当前类是泛型类
  2. class MyArray2<T> {
  3. public T[] array = (T[])new Object[10];
  4. public T getPos(int pos) {
  5. return array[pos];
  6. }
  7. public void setVal(int pos,T val) {
  8. this.array[pos] = val;
  9. }
  10. }

下面我们在<>中指定类型,此时就只能存放这个数据类型的数据了 

  1. public static void main(String[] args) {
  2. //<>中指定类型,此时这个类里面,只能放这个数据类型的数据
  3. MyArray2<String> myArray = new MyArray2<String>();
  4. myArray.setVal(0,"nb");
  5. myArray.setVal(1,"xawl");
  6. String s = myArray.getPos(1);
  7. String s1 = myArray.getPos(0);
  8. System.out.println(s);
  9. System.out.println(s1);
  10. }

 我们比如在定义一个Integer类型的

  1. MyArray2<Integer> myArray2 = new MyArray2<Integer>();
  2. myArray2.setVal(0,12);
  3. myArray2.setVal(1,13);
  4. Integer I = myArray2.getPos(0);
  5. System.out.println(I);

例子就写到这里

下面说一下泛型类语法

  1. 泛型类<类型实参> 变量名;//定义一个泛型类引用
  2. 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来转化很多种类型,那肯定是不行的,编译器从安全考虑不会让你通过的,

 所以我们正确的应该是这样做

  1. package Demo01;
  2. import java.lang.reflect.Array;
  3. import java.util.Arrays;
  4. class MyArray<T> {
  5. public T[] array;
  6. public MyArray(Class<T> clazz, int capacity) {
  7. array = (T[]) Array.newInstance(clazz,capacity);
  8. }
  9. public T[] getArray() {
  10. return array;
  11. }
  12. public void setVal(int pos,T val) {
  13. this.array[pos] = val;
  14. }
  15. public T getPos(int pos) {
  16. return this.array[pos];
  17. }
  18. }
  19. public class Test01 {
  20. public static void main(String[] args) {//指定数组类型是Integer
  21. MyArray<Integer> myArray = new MyArray<>(Integer.class,10);
  22. myArray.setVal(0,10);
  23. Integer[] tmp = myArray.getArray();
  24. System.out.println(Arrays.toString(tmp));
  25. }
  26. }

4.了解裸类型

下面看一下这个

我们写了一个泛型类,但是并没有带参数类型,而且也没报错,那么我们把这个叫做裸类型

 这里说明裸类型,是为了兼容老版本的API保留机制,我们不要自己去使用裸类型


5.泛型的上界

语法格式

class 泛型类名称<类型形参 extends 类型边界> {

......

}

 下面思考一下,如果要写一个泛型类,找数组中的最大值,应该怎么做

首先,肯定是直接比较不了的,因为T是引用数据类型,引用类型比较要用比较器

所以,

 

 这里也可以看到Comparable也是个泛型接口

所以泛型类可以这样写

  1. class Alg<T extends Comparable<T>> {
  2. public T findMaxVal(T[] array) {
  3. T maxVal = array[0];
  4. for (int i = 1; i < array.length; i++) {
  5. if (array[i].compareTo(maxVal) > 0) {
  6. maxVal = array[i];
  7. }
  8. }
  9. return maxVal;
  10. }
  11. }

6.泛型方法

语法格式

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

直接上例子

在前面我们找数组中的最大值,是在泛型类中写了一个方法,来实现的

 要调用这个方法,就必须要有个对象,如果不想有这个对象,那就可以把这个方法变成静态的就可以了,所以还有一种方法是

⚜️ 可以把这个方法写成静态的,然后放在普通类中

  1. class Alg2 {
  2. //静态方法
  3. public static<T extends Comparable<T>> T findMaxVal(T[] array) {
  4. T maxVal = array[0];
  5. for (int i = 1; i < array.length; i++) {
  6. if (array[i].compareTo(maxVal) > 0) {
  7. maxVal = array[i];
  8. }
  9. }
  10. return maxVal;
  11. }
  12. }

 ⚜️ 还有个方法是,继续写一个泛型方法,只不过这个方法也是成员方法

  1. class Alg3 {
  2. //泛型方法:成员方法
  3. public <T extends Comparable<T>> T findMaxVal2(T[] array) {
  4. T maxVal = array[0];
  5. for (int i = 1; i < array.length; i++) {
  6. if (array[i].compareTo(maxVal) > 0) {
  7. maxVal = array[i];
  8. }
  9. }
  10. return maxVal;
  11. }
  12. }

因为这个不是静态方法,所以还是要引用对象, 

  1. public static void main(String[] args) {
  2. Alg3 alg3 = new Alg3();
  3. Integer[] array = {100,200,24,34,55};
  4. int val = alg3.findMaxVal2(array);
  5. System.out.println(val);
  6. }

7.通配符(?)

7.1 理解通配符

通配符是用来解决泛型无法协变的问题的

协变指的是如果child是parent的子类,那么List<Child>也应该是List<Parent>的子类。但是泛型是不支持这样的父子类关系的

这是因为

  1. class MyArray<T> {
  2. public T[] array = (T[]) new Object[10];
  3. public T getPos(int pos) {
  4. return this.array[pos];
  5. }
  6. public void setval(int pos,T val) {
  7. this.array[pos] = val;
  8. }
  9. public T[] getArray() {
  10. return array;
  11. }
  12. }
  13. public class Test01 {
  14. public static void main(String[] args) {
  15. MyArray<Integer> myArray = new MyArray<>();
  16. System.out.println(myArray);
  17. MyArray<String> myArray2 = new MyArray<>();
  18. System.out.println(myArray2);
  19. }
  20. }

🤠 可以看到<>尖括号中的内容,不参与类型的组成。

所以在泛型中List<Child>不是List<Parent>的子类

下面看一个例子

  1. class Message<T> {
  2. private T message ;
  3. public T getMessage() {
  4. return message;
  5. }
  6. public void setMessage(T message) {
  7. this.message = message;
  8. }
  9. }
  10. public class Test02 {
  11. public static void main(String[] args) {
  12. Message<String> message = new Message() ;
  13. message.setMessage("xawl");
  14. fun(message);
  15. }
  16. public static void fun(Message<String> temp){
  17. System.out.println(temp.getMessage());
  18. }
  19. }

⚜️ 如果说此时泛型的类型不是String而是Integer

 🤠 所以这里就希望的是可以接收所有的泛型类型,但是又不能够让用户随意修改。这样就需要使用通配符‘’?‘’来解决

  1. public static void fun(Message<?> temp){
  2. System.out.println(temp.getMessage());
  3. }

所以通配符“?”表示,可以接收任意类型


在泛型中只有上界,没有下界

而在通配符?的基础上又产生了两个子通配符:

?extends类:设置通配符上界

?super类:设置通配符下界 


7.2 通配符上界

语法:

Vector<? extends 类型1> x = new Vector<类型2>();

类型1指定一个数据类型,那么类型2就只能是类型1或者是类型1的子类: 

Vector<? extends Number> x = new Vector<Integer>();

 下面看一个例子

  1. class Food {}
  2. class Fruit extends Food {}
  3. class Apple extends Fruit {}
  4. class Banana extends Fruit {}
  5. class Test<T> { // 设置泛型上限
  6. private T val ;
  7. public T getVal() {
  8. return val;
  9. }
  10. public void setVal(T Val) {
  11. this.val = val;
  12. }
  13. }
  14. public class Test03 {
  15. public static void main(String[] args) {
  16. Test<Apple> test = new Test<>() ;
  17. test.setVal(new Apple());
  18. fun(test);
  19. Test<Banana> test2 = new Test<>() ;
  20. test2.setVal(new Banana());
  21. fun(test2);
  22. }
  23. //只要是Fruit或者Fruit的子类即可
  24. public static void fun(Test<? extends Fruit> temp){
  25. System.out.println(temp.getVal());
  26. }
  27. }

⚜️  如果此时在fun函数中对temp添加元素,就会报错

 这是因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子类无法确定,所以添加元素就会报错,所以只能获取元素

总结:通配符的上界,不能进行写入数据,只能进行读取数据。 


7.3 通配符下界

语法:

Vector<? super 类型1> x = new Vector<类型2>();

类型1指定一个数据类型,那么类型2就只能是类型1或者是类型1的父类: 

Vector<? super Integer> x = new Vector<Number>();

下面看一个例子:

  1. class Food {}
  2. class Fruit extends Food {}
  3. class Apple extends Fruit {}
  4. class Banana extends Fruit {}
  5. class Test<T> { // 设置泛型上限
  6. private T val ;
  7. public T getVal() {
  8. return val;
  9. }
  10. public void setVal(T Val) {
  11. this.val = val;
  12. }
  13. }
  14. public class Test03 {
  15. public static void main(String[] args) {
  16. Test<Fruit> test = new Test<>() ;
  17. test.setVal(new Fruit());
  18. fun2(test);
  19. Test<Food> test2 = new Test<>() ;
  20. test2.setVal(new Food());
  21. fun2(test2);
  22. }
  23. public static void fun2(Test<? super Fruit> temp){
  24. System.out.println(temp.getVal());
  25. }

⚜️  如果此时在fun2函数中对temp添加元素,还会报错吗

  1. public static void fun2(Test<? super Fruit> temp){
  2. // 此时可以修改!!添加的是Fruit 或者Fruit的子类
  3. temp.setVal(new Apple());//这个是Fruit的子类
  4. temp.setVal(new Fruit());//这个是Fruit的本身
  5. // Fruit fruit = temp.getMessage(); 不能接收,这里无法确定是哪个父类
  6. System.out.println(temp.getVal());//只能直接输出
  7. }

总结:通配符的下界,不能进行读取数据,只能进行写入数据。  


 8.包装类

8.1基本数据类型对应包装类

在java中,因为基本类型不是继承Object,为了在泛型代码中可以支持基本类型,java给每个基本类型都搞了一个包装类型。

基本数据类型

包装类

byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean


8.2 装箱和拆箱

装箱:

  1. public static void main(String[] args) {
  2. int a = 20;
  3. Integer integer = a;//自动装箱
  4. Integer integer2 = new Integer(a);//显示装箱
  5. Integer integer3 = Integer.valueOf(a);//显示装箱
  6. System.out.println(integer);
  7. System.out.println(integer2);
  8. System.out.println(integer3);
  9. }

⚜️ 下面看一下,自动装箱的字节码文件

拆箱:

  1. public static void main(String[] args) {
  2. int a = 30;
  3. Integer integer = a;
  4. int val = integer;//自动拆箱
  5. System.out.println(val);
  6. int val2 = integer.intValue();//显示拆箱
  7. System.out.println(val2);
  8. double val3 = integer.doubleValue();//显示拆箱
  9. System.out.println(val3);
  10. }

⚜️ 下面看一下,自动拆箱的字节码文件


文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览44032 人正在系统学习中