Java中有限制的(上限、下限)通配符–向有限制通配符声明的集合中插入对象测试
学习通配符时,基于https://www.codenong.com/44516990/的例子,遇到了一些问题,做了编译测试并在测试基础上得出结论。
结论:
测试1、2、5对使用有限制的通配符的集合声明进行了测试,得出结论:使用通配符进行集合的声明便不要插入对象,这个时候编译器由于实现的是伪泛型会进行泛型擦除,最终把? super Sparrow替换成Sparrow。
测试3、4分别对集合的声明和定义以及作为方法参数的调用进行了测试,均符合预期效果。
测试用例关系如下:
此处直接使用java自带的泛型类List进行测试。
测试代码如下:
import java.util.ArrayList;
import java.util.List;
/**
* @author hyg
* @create 2021-07-02-21:39
*/
public class Birds {
static class Bird{}
static class Sparrow extends Bird{}
static class Color extends Bird{}
static class Color1 extends Sparrow{}
public static void main(String[] args){
//========编译测试1:<? extends Bird> 的列表中添加新对象======================================================================================
List<? extends Bird> birds = new ArrayList<Bird>();
birds.add(new Sparrow()); // #1 DOES NOT COMPILE
birds.add(new Bird());// #2 DOES NOT COMPILE
birds.get(0);// CAN COMPILE
// 编译错误原因:这里的 <? extends Bird> 只表示 任何一个Bird的子类 ,而不是所有的子类。重点在于一个类。
// 所以这个时候有可能是任何类,所以 <? extends Bird> 不能用add,只能get。即只能读不能写。
// 倘若需要add,则用super,如编译测试2所示。
//========编译测试2:<? super Bird> 的列表中添加新对象========================================================================================
List<? super Sparrow> birds = new ArrayList<>();
birds.add(new Sparrow()); // CAN COMPILE
birds.add(new Bird());// DOES NOT COMPILE 这里不能编译,因为<? super Sparrow>代表该list的泛型类型的范围是 Sparrow类及其父类, 再根据子类可以
birds.add(new Color());// DOES NOT COMPILE
birds.add(new Color1());// CAN COMPILE
System.out.println(birds.get(0));
System.out.println(birds.get(1));
//========编译测试3:有限制通配符最常用方法,符合super语义以及正常使用的一种===============================================================================
List<? super Sparrow> birds = null;
List<Bird> bird = new ArrayList<>();
List<Sparrow> sparrow = new ArrayList<>();
List<Color> color = new ArrayList<>();
List<Color1> color1 = new ArrayList<>();
birds = bird;// CAN COMPILE
birds = sparrow;// CAN COMPILE
birds = color;// DOES NOT COMPILE
birds = color1;// DOES NOT COMPILE
// 编译错误原因:<? super Sparrow> 表示的是Sparrow本身及其超类,这里只有Bird和Sparrow符合要求,Color和Color1不符合要求。
//========编译测试4:有限制通配符作为方法参数与泛型作为方法参数的对比===========================================================================
//--------------------------------------------1----------------------------
List<Sparrow> birds1 = new ArrayList<>();
birds1.add(new Sparrow());
birds1.add(new Color1());
callGenericParams(birds1);
callWildcardParams(birds1);
//Output:
//QuestionMark.Birds$Sparrow@1b6d3586
//QuestionMark.Birds$Color1@4554617c
//QuestionMark.Birds$Sparrow@1b6d3586
//QuestionMark.Birds$Color1@4554617c
//--------------------------------------------2----------------------------
List<Bird> birds2 = new ArrayList<>();
birds3.add(new Sparrow());
birds3.add(new Color1());
callWildcardParams(birds3);// DOES NOT COMPILE: 方法仅接受Sparrow及其子类,Bird不在范围内
//--------------------------------------------3----------------------------
List<Color1> birds3 = new ArrayList<>();
// birds4.add(new Sparrow());// DOES NOT COMPILE: 因为bird3这个List中存放子类Color及Color得子类的对象(普通多态)。这里的编译结果与测试2中
// <? super Sparrow> 的结果相同,进一步肯定了类型擦除时,编译器将<? super Sparrow> 替换为 <Sparrow>,所以往里
//面插入对象的时候只可以add Sparrow的对象以及Sparrow子类的对象。 测试5中进行完整对比测试。
birds3.add(new Color1());
birds3.add(new Color1());
callGenericParams(birds3);
callWildcardParams(birds3);
//Output:
//QuestionMark.Birds$Color1@74a14482
//QuestionMark.Birds$Color1@1540e19d
//QuestionMark.Birds$Color1@74a14482
//QuestionMark.Birds$Color1@1540e19d
// callGenericParams()和callWildcardParams()两个方法分别使用通配符和泛型声明,但效果相同。
//========编译测试5:往有限制通配符声明的集合中插入对象,编译器实际编译情况======================================================================
List<? super Sparrow> birds = new ArrayList<>();
birds.add(new Sparrow()); // CAN COMPILE
birds.add(new Bird());// DOES NOT COMPILE
birds.add(new Color());// DOES NOT COMPILE
birds.add(new Color1());// CAN COMPILE
List<Sparrow> sparrows = new ArrayList<>();
birds.add(new Sparrow()); // CAN COMPILE
birds.add(new Bird());// DOES NOT COMPILE
birds.add(new Color());// DOES NOT COMPILE
birds.add(new Color1());// CAN COMPILE
//根据上面编译情况可以看到两种声明 List<Sparrow>和List<? super Sparrow> 编译效果相同,实际上如果想要声明集合并插入对象时,使用有限的通配符,编译器
//是无法理解的,只能直接理解为super之后的对象,并按照普通的泛型Sparrow来进行声明和定义。
//结论:使用通配符基本上是使用在方法参数里面,或者普通的声明而不插入对象。 要插入对象就不用使用通配符。
}
//下面两个方法声明效果相同。
public static void callWildcardParams(List<? extends Sparrow> birds){
System.out.println(birds.get(0));
System.out.println(birds.get(1));
}
public static <E extends Sparrow> void callGenericParams(List<E> birds){
System.out.println(birds.get(0));
System.out.println(birds.get(1));
}
}
如有纰漏请提出,共同学习,谢谢。