一、前言
二、泛型类
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类型变量;
任意时候只能指定一种对象类型但是有父子概念,多态保证父类形参可以接收子类实参。
七、尾声
泛型,就是这么简单,完成了。
天天打码,天天进步!!!
网友评论