java泛型通配符问题。
java中的泛型基本用法参考《java编程思想》第四版 p.353
java泛型中比较难理解的主要是类型擦除和通配符相关。
1.类型擦除
在编译期间,类型信息会被擦除,可以认为类型的检测是在编译期间进行的(见例1)
List<String> list = new ArrayList<>();
list.add("123");
list.add(new Object());//编译器在编译的时候会检测到这个类型不匹配问题
所以在生成的class文件中不包含泛型中了类型信息。
因此下面的代码会报错
class Test{
public void method1(List<String> list){
System.out.println("String list");
}
public void method1(List<Integer> list){
System.out.println("Integer list");
}
}
因为类型擦除的缘故,所以method1中的参数List<String> 和List<Integer> 会被擦除为 List 因此 method1方法签名相同,所以报错。
另外,类型擦除将擦除到他的第一个边界,可以通过下面的例子来理解
class F{
public void f(){}
}
class Test{
public static <T> void method1(T t){
t.f(); //错误,无法确定传进来的参数就是类F的实例
}
public static <T extends F> void method2(T t){
t.f();//正确,因为编译器在编译期间就检查了参数t是否为类F或者类F的子类的实例 因此可以调用f() 方法
}
}
2.类型擦除与多态
考虑下面这个例子:
public class Main1 {
public static void main(String[] args) {
F z = new Z();
z.setValue("233");
}
}
class F<T>{
private T t;
public void setValue(T t){
System.out.println("F's setValue method called");
this.t = t;
}
}
class Z extends F<String>{
@Override
public void setValue(String s) {
System.out.println("Z's setValue method called");
super.setValue(s);
}
}
从类型擦除的方面考虑 类F的setValue方法经过类型擦除后实际的签名为 public void setValue(Object obj)。
所以预测的打印结果应该是:
F's setValue method called
实际结果为
Z's setValue method called
F's setValue method called,也就是说实现了子类方法覆盖父类方法。
这其中的原因是编译器替我们生成了桥方法(bridgemethod)
可以通过反射查看类Z中有哪些方法:
Method[] methods = Z.class.getDeclaredMethods();
for( Method method : methods ){
System.out.print( method.getReturnType().getSimpleName() + " " + method.getName() + "( " );
Parameter[] parameters = method.getParameters();
for( Parameter p : parameters ){
System.out.print( p.getType().getSimpleName() + " " );
}
System.out.println( ")" );
}
打印结果:
void setValue( String )
void setValue( Object )//桥方法
其实调用的是桥方法setValue( Object )覆盖了父类的方法,然后在该方法中调用void setValue( String )。
桥方法内容:
void setValue(Object str){
this.setValue( (String)str );
}
这样就实现了覆盖。
3.通配符
3.1上界
List<? extends Animal> list;//表示此list引用可以指向泛型参数为Animal或者其子类的List实现的实例。并不是说这个list容器可以添加Animal或者Animal子类的实例。(之前一直理解错误)
在看java泛型时,最让我难以理解的就是通配符的问题,不太好理解。通过一个例子解释一下。
前提:
class Animal{
public void walk(){}
}
class Cat extends Animal{}
class Dog extends Animal{}
class Alaska extends Dog{}
public class Main2 {
public static void main(String[] args) {
List<Cat> catList = new ArrayList<Cat>();
List<Dog> dogList = new ArrayList<Dog>();
testList(catList);
testList(dogList);
}
public static void testList(List<? extends Animal> list){
list.add( new Cat() );//错误
list.add( new Dog() );//错误
list.add( new Animal() )//错误
//正确
for( Animal animal : list){
animal.walk();
}
}
}
看上面这段代码,应该能有所理解。
假设声明为List<? extends Animal> 的 list 可以添加Animal及其子类,那么调用testList方法传入catList时,可以向catList中添 加Animal及其子类, 然而catList被声明为只能存放Cat类的实例,两者矛盾,因此 声明为List<? extends Animal> 的 list 无法添加Animal及其子类(因为无法确定接收的list参数的泛型参数是什么)。 但是可以确定传递给testList方法的list中存放的对象必定为Animal或者其子类,因此迭代改list并用Animal接收。
3.2下界
List<? super Dog> list //表示此list引用可以指向泛型参数为Dog或者其父类的List实现的实例。并不是说这个list容器可以添加Dog或者Dog父类的实例。
前提:
class Animal{
public void walk(){}
}
class Cat extends Animal{}
class Dog extends Animal{}
class Alaska extends Dog{}
public class Main2 {
public static void main(String[] args) {
testList1( new ArrayList<Dog>() );
testList1( new ArrayList<Animal>());
}
public static void testList1( List<? super Dog> list ){
list.add(new Dog());
list.add( new Alaska() );
list.add( new Animal() );//错误
for( Dog dog: list ){}//错误
}
}
testList1方法可以接受泛型参数为Dog或者Dog父类的List实现的实例(List<Dog>或者List<Animal>),那么在方法testList1中往list添加Dog类或者Dog类的子类的实例是可行的,因为可以确定传递给testList1方法的list容器可以存放Dog或者Dog的父类。
但是无法确定确定具体为哪个父类。因此对list迭代并用Dog引用接收是错误的。
文章中的结论均为个人思考总结,并非权威,如有错误,遗漏,望指正。
参考资料:
- 一天一个Java基础——泛型
这学期的新课——设计模式,由我仰慕已久的老师传授,可惜思维过快,第一节就被老师挑中上去敲代码,自此在心里烙下了阴影,都是Java基础欠下的债 这学期的新课——算法设计与分析,虽老师不爱与同学互动式的讲 ...
- Java 基础 -- 泛型、集合、IO、反射
package com.java.map.test; import java.util.ArrayList; import java.util.Collection; import java.util ...
- java基础-泛型举例详解
泛型 泛型是JDK5.0增加的新特性,泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数.这种类型参数可以在类.接口.和方法的创建中,分别被称为泛型类.泛型接口.泛型方法. 一.认识泛型 在没 ...
- Java基础 - 泛型详解
2022-03-24 09:55:06 @GhostFace 泛型 什么是泛型? 来自博客 Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了&quo ...
- java基础-泛型3
浏览以下内容前,请点击并阅读 声明 8 类型擦除 为实现泛型,java编译器进行如下操作进行类型擦除: 如果类型参数有限制则替换为限制的类型,如果没有则替换为Object类,变成普通的类,接口和方法. ...
- java基础 泛型
泛型的存在,是为了使用不确定的类型. 为什么有泛型? 1. 为了提高安全 2. 提高代码的重用率 (自动 装箱,拆箱功能) 一切好处看代码: package test1; import java.la ...
- java基础-泛型2
浏览以下内容前,请点击并阅读 声明 6 类型推测 java编译器能够检查所有的方法调用和对应的声明来决定类型的实参,即类型推测,类型的推测算法推测满足所有参数的最具体类型,如下例所示: //泛型方法的 ...
- java基础-泛型1
浏览以下内容前,请点击并阅读 声明 泛型的使用能使类型名称作为类或者接口定义中的参数,就像一般的参数一样,使得定义的类型通用性更强. 泛型的优势: 编译具有严格的类型检查 java编译器对于泛型代码的 ...
- Java基础---泛型、集合框架工具类:collections和Arrays
第一讲 泛型(Generic) 一.概述 1.JDK1.5版本以后出现的新特性.用于解决安全问题,是一个类型安全机制. 2.JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类 ...
- Java基础——泛型
一.定义 泛型(generic)是指参数化类型的能力.可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它(泛型实例化).使用泛型的主要优点是能够在编译时,而不是在运行时检测出错误.它是jd ...
随机推荐
- C# XML序列化方法和常用特性
/* C#对象XML序列化(一):序列化方法和常用特性 .Net Framework提供了对应的System.Xml.Seriazliation.XmlSerializer负责把对象序列化到XML,和 ...
- BAT级别分类
阿里的级别:P为技术岗,M为管理岗.P7是技术专家级别. 阿里级别对应薪资: 百度使用的T系列及对应薪资: 腾讯的T系列及对应薪资:
- PHP中使用 $_GET 与$_POST 实现简单的前后台数据传输交互功能
在之前的学习过程中我们接触过前后台数据请求交互的方法有表单提交.AJAX请求以及Angularjs中的$http,今天我们尝试在PHP中使用 $_GET 与$_POST 实现简单的前后台数据传输交互功 ...
- 如何让vim像IDE一样一键放大缩小字号?
原创,转载请注明出处 在其他IDE中,比如codeblocks,按住ctrl,然后滑动鼠标滚轮就可以实现字体的放大缩小. 在强大的vim中code怎么能缺少这种功能?! 在vim插件库中查询一番,发现 ...
- SpringMvc 关于 EXCEL
概述 我在使用SpingMvc 的EXCEL的发现传统的 AbstractJExcelView jexcel api已经过时 AbstractView poi Api 通过阅读官方文档发现建议我们使用 ...
- 【NOIP2015资源+题解】
数据下载(含cena配置文件+一套自己写的代码) 试题下载(pdf版) Day1 T1 Day1 T2 Day1 T3 Day2 T1 Day2 T2 Day3 T3
- flask + wtform + google storage
项目需要使用 flask 上传.下载文件到 google storage 上, 搜了一圈没有能直接结合 wtform 使用的插件,所以动手造了个轮子. 只实现了基本的上传,下载的功能,后续可能会完善预 ...
- Java 从键盘输入
package io; import java.io.*; public class ReadAndWrite { public static void main(String[] args) { I ...
- Java运行时内存划分与垃圾回收--以及类加载机制基础
----JVM运行时内存划分----不同的区域存储的内容不同,职责因为不同1.方法区:被线程共享,存储被JVM加载的类的信息,常量,静态变量等2.运行时常量池:属于方法区的一部分,存放编译时期产生的字 ...
- JAVA提高十:ArrayList 深入分析
前面一章节,我们介绍了集合的类图,那么本节将学习Collection 接口中最常用的子类ArrayList类,本章分为下面几部分讲解(说明本章采用的JDK1.6源码进行分析,因为个人认为虽然JDK1. ...