JAVA 泛型,
JAVA 泛型,
1、泛型是什么
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
上述描述摘自其他关于泛型文章中对泛型的阐释。阐释很到位,但是有点生涩。在实际用途中,泛型多用于List等容器类型,用于对容器的存储对象类型做约束。比如List<Integer> 列表中,只能存放Integer类型的对象,当然存在基础类型和引用类型装箱和拆箱的操作。
需要注意的是,JAVA中的泛型是伪泛型,它只是在编译期起作用,在运行期泛型是会被擦除,所以无法在运行时得知其类型参数的类型。也就是说,java中的泛型,只是为了让编码更安全,将原来可能要在运行期暴露出来的问题,提前到编译期暴露出来而已。
2、泛型的通配符
泛型的简单使用,这里不多做介绍。无非是可以用于接口,类,或者具体方法。
这里想重点解释的是泛型的通配符。
我们先来看如下这段代码
public class A {
Number[] numberArray;
Integer[] integerArray;
void test() {
numberArray = integerArray;
}
}
在编译期,这段代码并未报错。也就是说,java是允许把一个integer数组对象赋值给Number的数组对象的。这个很好理解,因为Integer是Number的子类,而java中的数组是c的。所以编辑期不存在任何问题。
public class A {
Number[] numberArray;
Integer[] integerArray = new Integer[5];
void test() {
numberArray = integerArray; //numberArray引用指向的是一个存放Integer的列表对象
numberArray[0]= 1.2d;
}
}
但是在实际中,谐变是存在安全隐患的,正如上述代码,一个存放Integer的列表对象,在经过谐变后,便可以骗过编译器,存放Double类型。
所以在后来的java版本中,也就是增加泛型的1.5版本,泛型摒弃了原来数组的设计,从原来数组的谐变,演变成泛型的不变。
public class A {
List<Number> numberList;
List<Integer> integerList;
void test() {
numberList = integerList; //报错
}
}
上述这段代码直接在编辑期就报错。泛型无法再将存放子类型的容器,赋值给存放父类型的容器。
那么如果此时,我们想对numberList,和integerList做相同对处理。
public class A {
List<Number> numberList;
List<Integer> integerList;
void test() {
//此处我想调用test1方法
this.test1(numberList); //不报错
this.test1(integerList); //报错
}
void test1(List<Number> param) {
}
}
上述代码integerList,则无法作为参数对象传进test1,那是不是意味着我们需要分别写两个方法呢?
答案肯定是否定的,这个时候泛型的通配符,就起作用了。
public class A {
List<Number> numberList;
List<Integer> integerList;
void test() {
//此处我想调用test1方法
this.test1(numberList); //不报错
this.test1(integerList); //不报错
this.test2(numberList); //不报错
this.test2(integerList); //不报错
}
void test1(List<? extends Number> param) {
}
void test2(List<? super Integer> param) {
}
}
3、<? extends Number> 和 <? super Number> 区别
<? extends Number> 顾名思义,该泛型为Number的子类,即param可以是List<Integer> 也可以是List<Number>,所以在执行上述代码时, this.test1(numberList) 或者 this.test1(integerList)都不会报错。
public class A {
List<Number> numberList;
List<Integer> integerList;
void test() {
this.test1(numberList); //不报错
this.test1(integerList); //不报错
}
void test1(List<? extends Number> param) {
param.forEach(this::process);
param.add(1);//报错
param.add(1.2d);//报错
}
void process(Number number) {
System.out.println(number);
}
}
但是在往param容器中增加对象时,不管是Integer还是Double,都会报错,这又是什么原因?
首先此处param可能是List<Integer>也可能是List<Double>或者其他,所以在往param容器增加对象时,该对象必须同时满足Integer,Double还有其他Number的子类。显然这样的一个对象是不存在的,所以执行param.add(1)或者param(1.2d)都会报错。
<? super Integer> 则表示泛型是Integer及其父类,那么该param可能是List<Integer> 或者List<Number>,List<Object> 或者其他。
public class A {
List<Number> numberList;
List<Integer> integerList;
void test() {
//此处我想调用test1方法
this.test1(numberList); //不报错
this.test1(integerList); //不报错
}
void test1(List<? super Integer> param) {
param.forEach(this::process);//报错
param.add(1);//不报错
param.add(new Object());//报错
}
void process(Number number) {
System.out.println(number);
}
}
所以我们在调用 this.test1(numberList) 或 this.test1(integerList) 都不会报错。但是我们在遍历param时,调用process就出错。process参数是Number类型的,但是param中,可能存的是object。那为什么往param中增加Integer类型的参数时不报错,增加Object类型的时候又报错,Object不应该也是Integer的父类吗?原因是Object 是Integer的父类没错,但是该param既要满足是Integer的类型,又要满足Object的类型,最终为了满足所有条件,它只能取最小集,也就是param中只能增加Integer类型的对象。
总结来说,<? extends Number> 只适合做读取操作,不适合做写入操作。<? super Number> 只适合做写入操作,不适合做读取操作,同时写入的对象必须是Number的子类,而非像字面理解的是Number的父类。
相关文章
- 暂无相关文章
用户点评