详解Java泛型

Posted 快到锅里来呀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解Java泛型相关的知识,希望对你有一定的参考价值。

目录

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给每个基本类型都搞了一个包装类型。

基本数据类型

包装类

byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean


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);
    

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


以上是关于详解Java泛型的主要内容,如果未能解决你的问题,请参考以下文章

Java 泛型泛型用法 ( 泛型编译期擦除 | 上界通配符 <? extends T> | 下界通配符 <? super T> )

Scala的泛型

201671010129 2016—2017—2 《Java程序设计》学习Java的泛型程序设计的小结

第六节:Java泛型

聊聊Java泛型

深入Java泛型(三泛型的上下边界)