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 ...
随机推荐
- 用于Mysql操作的MySqlHelper类
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Te ...
- Kettle文本文件输出和输入控件使用中,换行符导致的问题处理
1.如下图通过输入控件从数据库读取数据然后生成TXT文本文件,TXT文件生成原则是每一条数据生成一行数据,第二条数据换行保存 2.如下图所示,使用文本文件输入控件读入上图生成的文件,文件读入原则是按行 ...
- C# 解析 sln 文件
我的项目,编码工具 需要检测打开一个工程,获取所有项目. 但是发现原来的方法,如果存在文件夹,把项目放在文件夹中,那么是无法获得项目,于是我就找了一个方法去获得sln文件的所有项目. 原先使用的方法d ...
- UVa1605,Building for UN
我比较好奇的是uva后台是怎么测这题的 没什么可说的,那些不想敲但还是想直接交这题的直接copy过去吧 #include <iostream> #include <cstring&g ...
- Django(一)
Django 一.什么是web框架 框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统,简单地说,就是你用别人搭建好的舞台来做表演 ...
- JAVA中HashMap和Hashtable区别
Hashtable和HashMap在Java面试中相当容易被问到,甚至成为了集合框架面试题中最常被考的问题,所以在参加任何Java面试之前,都不要忘了准备这一题. 我们先看2个类的定义 public ...
- Java基础(00)
Java发展史 Java之父:詹姆斯.高斯林(James Gosling). SUN(Stanford University Network 斯坦福大学网络公司)产物. 1995年5月23日,java ...
- LeetCode 461. Hamming Distance (汉明距离)
The Hamming distance between two integers is the number of positions at which the corresponding bits ...
- spark-shell启动报错:Yarn application has already ended! It might have been killed or unable to launch application master
spark-shell不支持yarn cluster,以yarn client方式启动 spark-shell --master=yarn --deploy-mode=client 启动日志,错误信息 ...
- VB 用代码创建的控件和接收事件
在声明公共变量的位置加上这句就可以了 Dim WithEvents NewButton As Button form_load中添加 NewButton = New Button New ...