美文网首页程序员
想理解泛型吗?看这一篇就够了!

想理解泛型吗?看这一篇就够了!

作者: 套马杆的程序员 | 来源:发表于2020-12-06 22:23 被阅读0次

    一、前言
    二、泛型类
    2.1 概述
    Java中泛型使用情况大致包括三种:泛型类、泛型接口、泛型方法

    本节演示泛型类。

    2.2 代码
    2.2.1 泛型类一个字母、显式指定泛型实参
    package mypackage泛型类;

    //泛型类一个字母、显示指定泛型实参 则当前泛型由实参指定
    //此时,注意传入的实参必须为引用类型,不能为基本类型,如果是8种基本类型,则传入它们的包装类型
    public class Test {

    public static void main(String[] args) {
        GenericClass genericClass = new GenericClass<Integer>(123);
        GenericClass genericClass2 = new GenericClass<String>("abc");
        System.out.println(genericClass);
        System.out.println(genericClass2);
    }
    

    }

    class GenericClass<T> { // 定义泛型类 就是在定义类的时候,类名后面加上<T> 这个字母任意取 可以是T E V或者其他字母

    private T key;
    
    public T getKey() {
        return key;
    }
    
    public void setKey(T key) {
        this.key = key;
    }
    
    public GenericClass(T key) {
        super();
        this.key = key;
    }
    
    @Override
    public String toString() {
        return "GenericClass [key=" + key + "]";
    }
    

    }
    输出1:

    GenericClass [key=123]
    GenericClass [key=abc]
    小结1:泛型类一个字母、显示指定泛型实参 则当前泛型由实参指定
    注意:此时传入的实参必须为引用类型,不能为基本类型,如果是8种基本类型,则传入它们的包装类型

    2.2.2 泛型类一个字母,不显示指定泛型实参
    package mypackage泛型类2;

    //泛型类一个字母、不显示指定泛型实参
    public class Test {

    public static void main(String[] args) {
        // 虽然不显式指定T 但是传入参数的时候动态指定了T
        GenericClass genericClass = new GenericClass(123); // T为Integer
        GenericClass genericClass2 = new GenericClass("abc"); // T为String
        System.out.println(genericClass);
        System.out.println(genericClass2);
    
    }
    

    }

    class GenericClass<T> { // 定义泛型类 就是在定义类的时候,类名后面加上<T> 这个字母任意取 可以是T E V或者其他字母

    private T key;
    
    public T getKey() {
        return key;
    }
    
    public void setKey(T key) {
        this.key = key;
    }
    
    public GenericClass(T key) {
        super();
        this.key = key;
    }
    
    @Override
    public String toString() {
        return "GenericClass [key=" + key + "]";
    }
    

    }
    输出2:

    GenericClass [key=123]
    GenericClass [key=abc]
    小结2:泛型类一个字母、不显示指定泛型实参,虽然不显式指定T ,但是传入参数的时候动态指定了T

    2.2.3 泛型类两个字母、显式指定泛型实参
    package mypackage泛型类;

    //泛型类两个字母、显示指定泛型实参
    public class Test1 {

    public static void main(String[] args) {
        GenericClass1 genericClass = new GenericClass1<String, Integer>("小A", 95);
    
        System.out.println(genericClass);
    
    }
    

    }

    class GenericClass1<K, V> { // 定义泛型类 就是在定义类的时候,类名后面加上<T> 这个字母任意取 可以是T E V或者其他字母

    private K key;
    private V value;
    
    public K getKey() {
        return key;
    }
    
    public void setKey(K key) {
        this.key = key;
    }
    
    public V getValue() {
        return value;
    }
    
    public void setValue(V value) {
        this.value = value;
    }
    
    @Override
    public String toString() {
        return "GenericClass [key=" + key + ", value=" + value + "]";
    }
    
    public GenericClass1(K key, V value) {
        super();
        this.key = key;
        this.value = value;
    }
    

    }
    输出3:

    GenericClass [key=小A, value=95]
    小结3:泛型类两个字母、显示指定泛型实参,直接在一个字母扩展就好,同理多个字母都可以扩展

    2.2.4 泛型类两个字母,不显示指定泛型实参
    package mypackage泛型类2;

    //泛型类两个字母、不显示指定泛型实参
    public class Test1 {

    public static void main(String[] args) {
        // 虽然不显式指定T 但是传入参数的时候动态指定了T
        GenericClass1 genericClass = new GenericClass1("小A", 123); // T为Integer
        System.out.println(genericClass);
    
    }
    

    }

    class GenericClass1<K, V> { // 定义泛型类 就是在定义类的时候,类名后面加上<T> 这个字母任意取 可以是T E V或者其他字母

    private K key;
    private V value;
    
    public K getKey() {
        return key;
    }
    
    public void setKey(K key) {
        this.key = key;
    }
    
    public V getValue() {
        return value;
    }
    
    public void setValue(V value) {
        this.value = value;
    }
    
    @Override
    public String toString() {
        return "GenericClass1 [key=" + key + ", value=" + value + "]";
    }
    
    public GenericClass1(K key, V value) {
        super();
        this.key = key;
        this.value = value;
    }
    

    }
    输出4:

    GenericClass1 [key=小A, value=123]
    小结4:泛型类两个字母、不显示指定泛型实参,直接在一个字母扩展就好,同理多个字母都可以扩展

    2.3 小结
    本节演示泛型类,关于泛型接口、泛型方法且看下一节。

    三、泛型接口和泛型方法
    3.1 概述
    本节演示泛型接口和泛型方法。

    3.2 代码
    3.2.1 泛型接口一个字母
    package mypackage泛型接口;

    import java.util.Random;
    
    //泛型接口和泛型类的区别   
    //都是在定义的时候后面<T>  字母任意指定   区别就是具体类可以实例化对象,接口不能实例化对象,必须被类实现,然后才能拿到客户端运行
    public class Test {
       public static void main(String[] args) {
           GenericInterface generic = new GenericClass();
           System.out.println(generic.getT());
       }
    }
    interface GenericInterface<T> { // 定义接口的时候后面有<T>,则为泛型接口 字母可以任意指定
       // 接口中所有方法默认都是public
       T getT();
    }
    // 因为接口不能实例化对象,这里新建一个类实现接口,给客户端用 所以类也是泛型类
    class GenericClass implements GenericInterface<String> {
       private String[] provinces = new String[] { "湖南", "江西", "广东" };
    
       @Override
       public String getT() {
           return provinces[new Random().nextInt(3)];
       }
    
    }
    

    输出1:
    广东
    小结1:泛型接口不能实例化对象,所以定义GenericClass类实现GenericInterface接口,实现泛型接口实例化。

    3.2.2 泛型接口两个字母
    package mypackage泛型接口;

    import java.util.Random;
    
    //泛型接口和泛型类的区别   
    //都是在定义的时候后面<T>  字母任意指定   区别就是具体类可以实例化对象,接口不能实例化对象,必须被类实现,然后才能拿到客户端运行
    public class Test1 {
    
       public static void main(String[] args) {
           GenericInterface1 generic = new GenericClass1();
           System.out.println(generic.getK() + " - " + generic.getV());
       }
    
    }
    
    interface GenericInterface1<K, V> { // 定义接口的时候后面有<T>,则为泛型接口 字母可以任意指定
       // 接口中所有方法默认都是public
       K getK();
    
       V getV();
    }
    
    // 因为接口不能实例化对象,这里新建一个类实现接口,给客户端用 所以类也是泛型类
    class GenericClass1 implements GenericInterface1<String, String> {
       private String[] names = new String[] { "小A", "小B" };
       private String[] provinces = new String[] { "湖南", "江西", "广东" };
    
       public String getK() {
           return names[new Random().nextInt(2)];
       }
    
       public String getV() {
           return provinces[new Random().nextInt(3)];
       }
    
    }
    

    输出2:
    小A - 江西
    小结2:泛型接口两个字母,在一个字母上扩展即可

    3.2.3 泛型方法一个字母
    package mypackage泛型方法;

    //泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
    public class Test {
    
       public static void main(String[] args) {
           GenericClass genericClass = new GenericClass<Integer>(123);
           GenericClass genericClass2 = new GenericClass<String>("abc");
           System.out.println(genericClass);
           System.out.println(genericClass2);
           
           
           System.out.println(genericClass._displayKey(genericClass));
           System.out.println(genericClass2._displayKey(genericClass2));
       }
    
    }
    class GenericClass<T> { // 定义泛型类 就是在定义类的时候,类名后面加上<T> 这个字母任意取 可以是T E V或者其他字母
    
       private T key;
    
       public T getKey() {
           return key;
       }
    
       public void setKey(T key) {
           this.key = key;
       }
    
       public GenericClass(T key) {
           super();
           this.key = key;
       }
    
       @Override
       public String toString() {
           return "GenericClass [key=" + key + "]";
       }
      /** 
        * 这是一个真正意义上的泛型方法。
        * 泛型方法标志:在访问控制符与返回值之间的<T>不可或缺,表明这是一个泛型方法,并且声明了一个泛型T
        * 这个T声明之后,这个T就可以出现在这个泛型方法的任意位置.
        */
       public <T>  T _displayKey(GenericClass<T> genericClass){
           System.out.println("===============泛型方法============");
           return genericClass.getKey();
       }
       //泛型方法的几种错误:
       //错误一,没有在返回值前尖括号<>中声明的字母不能用,否则编译报错
    //  public <T> T _displayKey1(GenericClass<E> genericClass){  //E cannot be resolved to a type
    //      return genericClass.getKey();
    //  }
    //  public <T> E _displayKey2(GenericClass<T> genericClass){  //E cannot be resolved to a type
    //      return genericClass.getKey();
    //  }
       //错误二:没有在返回值前尖括号这个标志的都不是泛型方法
       //这不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。   关键:没有在返回值前尖括号这个标志
       public void showKeyValue1(GenericClass<Number> obj){
         System.out.println("泛型测试 key value is " + obj.getKey());
       }
    
       //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?    关键:没有在返回值前尖括号这个标志
       public void showKeyValue2(GenericClass<?> obj){
         System.out.println("泛型测试 key value is " + obj.getKey());
       }
    }
    

    输出3:
    GenericClass [key=123]
    GenericClass [key=abc]
    ===============泛型方法============
    123
    ===============泛型方法============
    abc
    小结3:泛型类,是在实例化类的时候指明泛型的具体类型;同理,泛型方法,是在调用方法的时候指明泛型的具体类型 。意义上是一样的,泛型字母都是先声明后使用,只是范围不同,泛型类、泛型接口定义时声明的字母对类或接口内容均有效,泛型方法定义时声明的字母对方法内容有效

    金手指:
    泛型方法: 在访问控制符和返回值之间
    解释:因为在返回值之前只有关键字,不会使用到泛型T,但是从返回值开始,
    就开始使用到泛型T了,毕竟返回值自己就使用泛型T
    判断真假泛型方法的key:是否在返回值前有尖括号这个标志

    3.2.4 泛型方法两个字母
    package mypackage泛型方法;

    public class Test1 {
       public static void main(String[] args) {
           GenericClass1 genericClass = new GenericClass1<String, Integer>("小A", 95);
           System.out.println(genericClass);
           System.out.println(genericClass._displayKey(genericClass));
       }
    }
    
    class GenericClass1<K, V> { // 定义泛型类 就是在定义类的时候,类名后面加上<T> 这个字母任意取 可以是T E V或者其他字母
    
       private K key;
       private V value;
    
       public K getKey() {
           return key;
       }
    
       public void setKey(K key) {
           this.key = key;
       }
    
       public V getValue() {
           return value;
       }
    
       public void setValue(V value) {
           this.value = value;
       }
    
       @Override
       public String toString() {
           return "GenericClass [key=" + key + ", value=" + value + "]";
       }
    
       public GenericClass1(K key, V value) {
           super();
           this.key = key;
           this.value = value;
       }
    
       /**
        * 这是一个真正意义上的泛型方法。 泛型方法标志:在访问控制符与返回值之间的<T>不可或缺,表明这是一个泛型方法,并且声明了一个泛型T
        * 这个T声明之后,这个T就可以出现在这个泛型方法的任意位置.
        */
       public <K, V> GenericClass1<K, V> _displayKey(GenericClass1<K, V> genericClass) {
           System.out.println("===============泛型方法============");
           return genericClass;
       }
    
    }
    

    输出4:

    GenericClass [key=小A, value=95]
    ===============泛型方法============
    GenericClass [key=小A, value=95]
    小结4:泛型方法两个字母,在一个字母上扩展即可

    3.3 小结
    本节介绍泛型接口和泛型方法,演示了一个泛型字母、两个泛型字母,多个泛型字母可以依次类推。

    四、泛型通配符与上下限、泛型擦除
    4.1 概述
    本节介绍泛型通配符与上下限、泛型两种擦除方式。且见代码1、代码2、代码3.

    4.2 代码
    4.2.1 泛型通配符与上下限
    package mypackage泛型通配符和上下限;

    import java.util.ArrayList;
    import java.util.List;
    
    public class Test {
    
       public static void main(String[] args) {
           List<Integer> _iIntegers_list = new ArrayList<>();
           List<String> _sStrings_list = new ArrayList<>();
           List<Number> _nNumbers_list = new ArrayList<>();
           List<Object> _oObjects_list = new ArrayList<>();
    
           function1(_iIntegers_list);// function1接收的实参是Number及其子类 Integer是Number子类
                                       // 这里正确
           // function1(_sStrings_list);//function1接收的实参是Number及其子类 String与Number无关
           // 这里错误
           function1(_nNumbers_list);// function1接收的实参是Number及其子类 这里Number正确
           // function1(_oObjects_list);//function1接收的实参是Number及其子类 Object是Number父类
           // 这里错误
    
           // function2(_iIntegers_list);//function2接收的实参是Number及其父类
           // Integer是Number子类 这里错误
           // function2(_sStrings_list);//function2接收的实参是Number及其父类 String与Number无关
           // 这里错误
           function2(_nNumbers_list);// function2接收的实参是Number及其父类 这里Number正确
           function2(_oObjects_list);// function2接收的实参是Number及其父类 Object是Number父类
                                       // 这里正确
    
           function3(_iIntegers_list); // function3接收的实参是?是一种类型实参,可以看做所有类的父类
           function3(_sStrings_list);// function3接收的实参是?是一种类型实参,可以看做所有类的父类
           function3(_nNumbers_list);// function3接收的实参是?是一种类型实参,可以看做所有类的父类
           function3(_oObjects_list);// function3接收的实参是?是一种类型实参,可以看做所有类的父类
       }
    
       // 这不是一个泛型方法,返回值前面没有尖括号<T>,泛型上限 此时泛型是?,?是Number及其子类 接收的实参是Number及其子类
       private static void function1(List<? extends Number> list) {
           System.out.println("function1");
       }
    
       // 这不是一个泛型方法,返回值前面没有尖括号<T>,泛型下限 此时泛型是?,?是Number及其父类 接收的实参是Number及其父类
       private static void function2(List<? super Number> list) {
           System.out.println("function2");
       }
    
       // 泛型上限extends的产生是因为泛型之间没有类似多态的思想,
       // 泛型为Number只能接收Number,不能接收Integer,即使Intger是Number子类,所有有了泛型上限
       // 泛型下限与泛型上限相对应,但使用super关键字
       // 演示通配符 ?是一种类型实参,可以看做所有类的父类
       private static void function3(List<?> list) {
           System.out.println("function3");
       }
    }
    

    输出1:

    function1
    function1
    function2
    function2
    function3
    function3
    function3
    function3
    

    小结1:因为泛型没有类似多态的想法,所以产生了通配符?和上下限(extends super).

    泛型上限extends的产生是因为泛型之间没有类似多态的思想
    泛型为Number只能接收Number,不能接收Integer,
    即使Integer是Number子类,所以有了泛型上限
    泛型下限与泛型上限相对应,但使用super关键字
    演示通配符 ?是一种类型实参,可以看做所有类的父类

    4.2.2 泛型擦除(自动擦除)
    package mypackage泛型擦除;

    import java.util.ArrayList;
    import java.util.List;
    
    //泛型擦除包括自动擦除和手动擦除
    //自动擦除是指泛型仅在编译时有效,运行时则是相同的类型                                       编译之后程序会自动去泛型,即称为自动擦除
    //手动擦除:将有泛型的集合赋给不带泛型的集合,此时泛型被擦除(手动擦除).    
    //注意:所有的泛型报错都是编译时报错,因为泛型只在编译时有效
    
    //演示自动擦除
    public class Test {
    
       public static void main(String[] args) {
           List<String> list = new ArrayList<>();
           // list.add(123); //报错,编译时报错,因为类型和泛型不同,说明泛型在编译时是有效的
           // 注意:所有泛型报错都是编译时报错,因为泛型只在编译时有效
           List<Integer> list2 = new ArrayList<>();
           System.out.println(list.getClass() + " - " + list2.getClass());
           if (list.getClass().equals(list2.getClass())) {
               System.out.println("类型相同"); // 打印这句,说明泛型在运行时无效了,因为编译后就被擦除了
           }
       }
    
    }
    

    输出2:
    class java.util.ArrayList - class java.util.ArrayList
    类型相同
    小结2:泛型只在编译时有效,编译后自动擦除。

    4.2.3 泛型擦除(手动擦除)
    package mypackage泛型擦除;

    import java.util.ArrayList;
    import java.util.List;
    
    //泛型擦除包括自动擦除和手动擦除
    //自动擦除是指泛型仅在编译时有效,运行时则是相同的类型                                       编译之后程序会自动去泛型,即称为自动擦除
    //手动擦除:将有泛型的集合赋给不带泛型的集合,此时泛型被擦除(手动擦除).    
    //注意:所有的泛型报错都是编译时报错,因为泛型只在编译时有效
    
    //演示泛型手动擦除
    public class Test1 {
    
       public static void main(String[] args) {
           List<String> list = new ArrayList<String>();
           list.add("abc");
    
           List list2 = list; // 将有泛型的集合赋给不带泛型的集合,此时泛型被擦除(手动擦除).
           list2.add(123); // 这里编译时没有报错,完美证明泛型已经被擦除
    
           for (Object object : list2) {
               System.out.println(object);
           }
    
       }
    
    }
    

    输出3:
    abc
    123
    小结3:将有泛型的集合赋给不带泛型的集合,此时泛型被擦除(手动擦除).

    4.3 小结
    本节介绍泛型通配符和上下限,泛型两种擦除方式。

    五、泛型之堆污染
    5.1 概述
    Java堆污染的定义:Heap pollution(堆污染), 指的是当把一个不带泛型的对象赋值给一个带泛型的变量时, 就有可能发生堆污染.

    Java堆污染的原因:因为在定义泛型对象(泛型类的对象)时,是否显示指定泛型类型不是强制的(可以在后面的运行中确定),这就造成了ClassCastException隐患,这个异常隐患不会在编译时抛出,而是在运行时抛出。

    5.2 代码
    5.2.1 未带泛型造成的堆污染
    package mypackage堆污染;

    import java.util.*;
    //第一种,未指定泛型造成的堆污染
    //将未显示指定泛型的对象,运行中确定范型后,赋值给与其泛型不同的对象,而造成的堆污染
    public class Test {
    
       public static void main(String[] args) {
       List list=new ArrayList();
       list.add(123);
       List<String> list2=list;//这里将不带泛型的list集合再次赋值给带泛型String的list集合    产生了堆污染
       System.out.println(list2.get(0)); //堆污染出现
    
       }
    
    }
    

    输出1:

    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at mypackage堆污染.Test.main(Test.java:12)
    小结1:堆污染产生只有一个原因,就是把一个不带泛型的对象赋值给一个带泛型的变量,代码1演示最原始的情况。

    附:代码1抑制堆污染(编译时去掉警告)——加上@SuppressWarnings(“unchecked”)就好:

    package mypackage堆污染;

    import java.util.*;
    //第一种,未指定泛型造成的堆污染
    //将未显示指定泛型的对象,运行中确定范型后,赋值给与其泛型不同的对象,而造成的堆污染
    
    public class Test {
    
       @SuppressWarnings({ "unchecked", "rawtypes" })
       public static void main(String[] args) {
       List list=new ArrayList();
       list.add(123);
       List<String> list2=list;//这里将不带泛型的list集合再次赋值给带泛型String的list集合    产生了堆污染
       System.out.println(list2.get(0)); //堆污染出现
    
       }
    
    }
    

    如果因为泛型擦除而造成不带泛型,进入被带泛型变量步骤,最后造成堆污染,且看代码2.

    5.2.2 泛型擦除后泛型消失造成的堆污染
    package mypackage堆污染;

    import java.util.ArrayList;
    import java.util.List;
    //第二种,手动擦除后造成的堆污染
    public class Test1 {
    
       public static void main(String[] args) {
           List<Integer> intList = new ArrayList<>();
           intList.add(1);
    
           List list = intList;  //带泛型的list集合赋值给无泛型的list集合,这是泛型的手动擦除
           List<String> lst = list;//这里将擦除后不带泛型的list集合再次赋值给带泛型String的list集合    产生了堆污染
    
           System.out.println(lst.get(0));   //这里体现了堆污染  ClassCastException
       }
    
    }
    

    输出2:

    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at mypackage堆污染.Test1.main(Test1.java:15)
    小结2:堆污染产生只有一个原因,就是把一个不带泛型的对象赋值给一个带泛型的变量,代码2演示开始带泛型list,然后手动擦除泛型,最后被不带泛型的对象赋值,造成堆污染。

    附:代码2抑制堆污染(编译时去掉警告)——加上@SuppressWarnings(“unchecked”)就好:

    package mypackage堆污染;

    import java.util.ArrayList;
    import java.util.List;
    //第二种,手动擦除后造成的堆污染
    public class Test1 {
    
       @SuppressWarnings({ "unchecked", "rawtypes" })
       public static void main(String[] args) {
           List<Integer> intList = new ArrayList<>();
           intList.add(1);
    
           List list = intList;  //带泛型的list集合赋值给无泛型的list集合,这是泛型的手动擦除
           List<String> lst = list;//这里将擦除后不带泛型的list集合再次赋值给带泛型String的list集合    产生了堆污染
    
           System.out.println(lst.get(0));   //这里体现了堆污染  ClassCastException
       }
    
    }
    

    5.2.3 泛型数组作为函数参数造成的堆污染
    package mypackage堆污染;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    //第三种,因为泛型数组而带来的堆污染
    //java 语言中不允许创建泛型数组, 所以当可变参数为带泛型的可变数组时, 方法内只能用不带泛型的数组接收.     将带泛型的数组赋值给不带泛型数组,造成堆污染
    public class Test2 {
    
       public static void main(String[] args) {
           List<String> list = new ArrayList<>();
           list.add("abc");
           _function(list);
    
       }
    
       public static void _function(List<String>... sLists) { //
           List[] _listArray = sLists; // //java 语言中不允许创建泛型数组, 方法内只能用不带泛型的数组接收.
           // 但是参数sLists又是带泛型的集合,这里将带泛型的集合赋值给不带泛型的数组,有代码2中擦除的味道,为后面造成隐患
           List<Integer> _tempList = Arrays.asList(1);
    
           _listArray[0] = _tempList;// 这里是关键,_tempList有泛型Integer,_listArray无泛型,
                                       // 将带泛型的list集合赋值给不带泛型list数组,造成堆污染
    
           String string = sLists[0].get(0); // 每次都是这里,取出来的时候报错
    
       }
    }
    

    输出3:

    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at mypackage堆污染.Test2._function(Test2.java:26)
    at mypackage堆污染.Test2.main(Test2.java:14)
    小结3:堆污染产生只有一个原因,就是把一个不带泛型的对象赋值给一个带泛型的变量,代码3演示因为泛型数组造成的堆污染,因为java语言中不允许创建泛型数组, 方法内只能用不带泛型的数组接收带泛型的数组实参,有代码2中泛型擦除的味道,后面又将带Integer泛型的_templist赋值给不带泛型的_listarray,正式造成堆污染,最后get(0)并赋值的时候这个堆污染的错误显示出来。

    附:代码3抑制堆污染(编译时去掉警告)——加上@SuppressWarnings(“unchecked”)就好:

    package mypackage堆污染;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    //第三种,因为泛型数组而带来的堆污染
    //java 语言中不允许创建泛型数组, 所以当可变参数为带泛型的可变数组时, 方法内只能用不带泛型的数组接收.     将带泛型的数组赋值给不带泛型数组,造成堆污染
    public class Test2 {
    
       @SuppressWarnings("unchecked")
       public static void main(String[] args) {
           List<String> list = new ArrayList<>();
           list.add("abc");
           _function(list);
    
       }
    
       @SuppressWarnings({ "unused", "rawtypes", "unchecked" })
       public static void _function(List<String>... sLists) { //
           List[] _listArray = sLists; // //java 语言中不允许创建泛型数组, 方法内只能用不带泛型的数组接收.
           // 但是参数sLists又是带泛型的集合,这里将带泛型的集合赋值给不带泛型的数组,有代码2中擦除的味道,为后面造成隐患
           List<Integer> _tempList = Arrays.asList(1);
    
           _listArray[0] = _tempList;// 这里是关键,_tempList有泛型Integer,_listArray无泛型,
                                       // 将带泛型的list集合赋值给不带泛型list数组,造成堆污染
    
           String string = sLists[0].get(0); // 每次都是这里,取出来的时候报错
    
       }
    }
    

    5.3 小结
    本节演示了泛型堆污染,三种方式产生的泛型堆污染,本质上就是一个意思“把一个不带泛型的对象赋值给一个带泛型的变量时, 就有可能发生堆污染.”

    六、泛型和多态区别
    Java语言中,泛型和多态是两个完全不同东西,没有任何交集。

    Java中泛型是伴随集合框架和产生的,是从Java5开始支持的新的语法,为实现广泛通用的类型.(集合框架或自定义的泛型类、泛型接口、泛型方法)

    多态即向上转型,所谓的上和下的产生是由类与类、类与接口之间的继承、实现联系在一起的,继承实现是多态的基础。Java实现运行时绑定,只有在运行时才可以知道一个引用真正指向的对象类型,编译时是不知道的。

    综上,泛型和多态的区别:

    1、泛型总是和集合框架联系在一次(当然,程序员也可以自定义泛型类、泛型方法、泛型接口);

    多态总是和类与类、类与接口、接口与接口的继承实现联系在一起,是一种向上转型。

    2、泛型是编译时有效,编译后自动擦除;多态一般是运行时绑定,运行时有效。

    3、任意时刻只能指定一种泛型且泛型没有父子概念,泛型为Number是不可以接收子类Integer类型变量;

    任意时候只能指定一种对象类型但是有父子概念,多态保证父类形参可以接收子类实参。

    七、尾声
    泛型,就是这么简单,完成了。

    天天打码,天天进步!!!

    相关文章

      网友评论

        本文标题:想理解泛型吗?看这一篇就够了!

        本文链接:https://www.haomeiwen.com/subject/sbmawktx.html