java泛型详解(接口与方法、通配符、堆污染、多态---含代码!)

一、前言

二、泛型类

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类型变量;

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

七、尾声

泛型,就是这么简单,完成了。

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

猜你喜欢

转载自blog.csdn.net/Java_Yhua/article/details/110838328