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-基础-泛型的更多相关文章

  1. 一天一个Java基础——泛型

    这学期的新课——设计模式,由我仰慕已久的老师传授,可惜思维过快,第一节就被老师挑中上去敲代码,自此在心里烙下了阴影,都是Java基础欠下的债 这学期的新课——算法设计与分析,虽老师不爱与同学互动式的讲 ...

  2. Java 基础 -- 泛型、集合、IO、反射

    package com.java.map.test; import java.util.ArrayList; import java.util.Collection; import java.util ...

  3. java基础-泛型举例详解

    泛型 泛型是JDK5.0增加的新特性,泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数.这种类型参数可以在类.接口.和方法的创建中,分别被称为泛型类.泛型接口.泛型方法. 一.认识泛型 在没 ...

  4. Java基础 - 泛型详解

    2022-03-24 09:55:06 @GhostFace 泛型 什么是泛型? 来自博客 Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了&quo ...

  5. java基础-泛型3

    浏览以下内容前,请点击并阅读 声明 8 类型擦除 为实现泛型,java编译器进行如下操作进行类型擦除: 如果类型参数有限制则替换为限制的类型,如果没有则替换为Object类,变成普通的类,接口和方法. ...

  6. java基础 泛型

    泛型的存在,是为了使用不确定的类型. 为什么有泛型? 1. 为了提高安全 2. 提高代码的重用率 (自动 装箱,拆箱功能) 一切好处看代码: package test1; import java.la ...

  7. java基础-泛型2

    浏览以下内容前,请点击并阅读 声明 6 类型推测 java编译器能够检查所有的方法调用和对应的声明来决定类型的实参,即类型推测,类型的推测算法推测满足所有参数的最具体类型,如下例所示: //泛型方法的 ...

  8. java基础-泛型1

    浏览以下内容前,请点击并阅读 声明 泛型的使用能使类型名称作为类或者接口定义中的参数,就像一般的参数一样,使得定义的类型通用性更强. 泛型的优势: 编译具有严格的类型检查 java编译器对于泛型代码的 ...

  9. Java基础---泛型、集合框架工具类:collections和Arrays

    第一讲     泛型(Generic) 一.概述 1.JDK1.5版本以后出现的新特性.用于解决安全问题,是一个类型安全机制. 2.JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类 ...

  10. Java基础——泛型

    一.定义 泛型(generic)是指参数化类型的能力.可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它(泛型实例化).使用泛型的主要优点是能够在编译时,而不是在运行时检测出错误.它是jd ...

随机推荐

  1. SVN版本控制图标经常延时显示或未显示问题解决方法

    项目中,使用svn经常遇到,文件或文件夹图标延时显示或未显示的问题,终于找到办法解决 客户端:TortoiseSVN

  2. codesmith连接Mysql提示“找不到请求的 .Net Framework Data Provider。可能没有安装。"

    1,首先需要将MySql.Data.dll复制到codesmith安装目录下bin文件夹下,注意dll的版本 2,其次因为codesmith7采用的是.net4.0的配置文件,(64位系统)找到C:\ ...

  3. android Intent机制详解

    http://www.oschina.net/question/565065_67909 http://www.cnblogs.com/hummersofdie/archive/2011/02/12/ ...

  4. 【完美解决】2017打开MVC 4项目,cshtml页面提示‘当前上下文不存在名称model’

    时间:2017/10/19 背景:领导让再之前的MVC 4老项目上新增功能,从GIT上拉取下来,使用VS2017打开之后,cshtml界面所有和Razor相关的代码均被提示‘当前上下文不存在名称XXX ...

  5. word2vec 在 非 自然语言处理 (NLP) 领域的应用

    word2vec 本来就是用来解决自然语言处理问题的,它在 NLP 中的应用是显然的. 比如,你可以直接用它来寻找相关词.发现新词.命名实体识别.信息索引.情感分析等:你也可以将词向量作为其他模型的输 ...

  6. (10.23)Java小知识!

    ---恢复内容开始--- 方法的定义: 一般情况下,定义一个方法包含以下语法: 修饰符 返回值类型 方法名 (参数类型 参数名 , ...){ ... 方法体 ... return 返回值; } 修饰 ...

  7. Kotlin——最详细的数据类型介绍

    任意一种开发语言都有其数据类型,并且数据类型对于一门开发语言来说是最基本的构成,同时也是最基础的语法.当然,kotlin也不例外.kotlin的数据类型和Java是大致相同的,但是他们的写法不同,并且 ...

  8. Spark 作业调度相关术语

    作业(Job):RDD 中由行动操作所生成的一个或多个调度阶段 调度阶段(Stage):每个作业会因为 RDD 间的依赖关系拆分成多组任务集合,称为调度阶段,也叫做任务集(TaskSet).高度阶段的 ...

  9. 容器与Docker简介(二)什么是DOCKER——微软微服务电子书翻译系列

    Docker是一个开源项目,用于将应用程序部署自动化,作为可在云端或本地运行的可移植,自包含的容器. Docker同时也是一家促进和发展这项技术的公司,与云,Linux以及Windows的供应商(包括 ...

  10. Java运行时内存划分与垃圾回收--以及类加载机制基础

    ----JVM运行时内存划分----不同的区域存储的内容不同,职责因为不同1.方法区:被线程共享,存储被JVM加载的类的信息,常量,静态变量等2.运行时常量池:属于方法区的一部分,存放编译时期产生的字 ...