初识Java泛型以及桥接方法
泛型的由来
在编写程序时,可能会有这样的需求:
容器类,比如java中常见的list等。为了使容器可以保存多种类型的数据,需要编写多种容器类,每一个容器类中规定好了可以操作的数据类型。
此时可能会有IntList、LongList、StringList、XXXList等多个List容器类,但是容器类内部的各个方法,除了add、remove、get方法操作的数据类型不同意外,几乎全部相同。为了方便起见,可以使用ObjectList,但是在get的数据是Object类型,需要进行强制类型转换,这就会造成一些程序运行时的隐患。
通过查询网络资料时,发现这个需求对于泛型的需要最迫切。于是在jdk1.5(2004年)中,java加入了泛型。
什么是泛型
泛型:参数化类型。将类类型,作为参数,进行传递。
泛型参数,可以为多个,使用尖括号括起来。
比如,通过实例化List容器时,指定容器类类型String,实现了String容器。
List<String> list= new LinkedList<String>();
list.add("1");//没有编译错误
list.add(1);//编译错误
如何实现泛型
在java中,可实现泛型接口、泛型类、泛型方法。
1、泛型接口:常见的List接口,Map接口,都是泛型接口。
举例:
public interface Test<T>{
public void add(T element);
public T get(int index);
}
2、泛型类:LinkedList、ArrayList、HashMap、TreeMap等都是泛型类,并且他们实现了泛型接口;
举例:
public class Target<T, E> implements Test<T>{
private Object[] list=new Object[100];
private int index=0;
public void add(T element){
list[index++]=element;
}
public T get(int index){
return (T)list[index];
}
}
3、泛型方法:同泛型接口和泛型类的定义稍微有些不同。
举例:
public <T> T add( Class<T> clz){
T instance=clz.newInstance();
return instance
}
在定义泛型接口、类、方法时,可以添加多个泛型形参,一般使用大写字母表示,并用逗号分隔,泛型定义处,使用尖括号将所有泛型形参括起来。
泛型接口、类的泛型形参,放置在接口或类名的后面。
泛型方法的泛型形参,放置在方法返回类型的前面即可。
注意,java的泛型属于code share,只是在编译时使用,用于检查传递给泛型类、方法的数据类型是否符合泛型定义,泛型类和方法中的泛型会在编译成功后擦出,成为Object类型,编译器并会在泛型数据操作时,自动添加类型转换。由于编译期间保证了泛型类型的正确使用,因此自动添加的类型转换是安全的。
举例:
使用List容器,调用其方法
public class Main{
public static void main(String[] args){
List<Long> ttt=new LinkedList<Long>();
tt.add(1L);
Long value=tt.get(0);
}
}
在编译后的class文件,可翻译为:
public class Main{
public static void main(String[] args){
List ttt=new LinkedList();
tt.add(1L);
Long value=(Long)tt.get(0);
}
}
编译时,编译器会检查凡是使用泛型类实例的地方,是不是按照泛型的规定,进行传参的。
比如ttt.add(1L),编译器会检查传入的参数是不是Long型;还有tt.get(0),编译器会自动加上类型转换。最终看到编译后,泛型类的泛型被擦出了。
类型擦出导致,以下输出会为true,因为泛型类不会因为不同的泛型类型,导致生成不同的类。
List<String> list1=new LinkedList<String>();
List<Integer> list2=new LinkedList<Integer>();
System.out.println(list2.getClass()==list1.getClass());//输出为true
泛型定义时的约束
定义泛型形参时,可以使用extends限制泛型的范围。
比如使用<T extends K>,表示T必须是类K的子类,或者接口K的实现类。
比如使用<T super K>,表示T必须是类K的父类,或类K实现的接口。
注意:extends并不表示类的继承含义,只是表示泛型的范围关系。
注意:extends中可以指定多个范围,实行泛型类型检查约束时,会以最左边的为准。
java泛型实现的原理
在C++中也有泛型的概念,但java的泛型实现原理同C++是不一样的。
泛型实现原理有两种:
1、为使用的泛型类,单独生成一份非泛型的具体类。
比如List<String> list=new ArrayList<String>,在程序编译时,会单独生成一份StringArrayList类,并且类里面所有的操作元素都是String类。
2、采用擦出机制,泛型接口或类中,所有使用泛型形参的地方,全部擦除,替换为Object类型(java中所有类的父类)。所有相同泛型类的实例共享使用泛型类的代码。在泛型类的实例进行数据操作的地方(泛型类外部,自定义的程序部分),由编译器检查操作的参数是否为泛型类实例定义时的类类型,必要时自动添加强制类型转换。
在擦除时,如果<T extends XXClass>,T擦出后会变为XXClass。
如果<T extends AClass,BClass>,T擦出后,类型为AClass,以最左边的类型为最终类型,这点要注意。
桥接方法
首先举一个例子。
public class A<T>{
public T get(T a){
//a进行一些操作
return a;
}
}
public class B extends A<String>{
@override
public String get(String a){
//a进行一些操作
return a;
}
}
由于类型擦出机制的存在,按理说编译后的文件在翻译为java应如下所示
public class A{
public Object get(Object a){
//a进行一些操作
return a;
}
}
public class B extends A{
@override
public String get(String a){
//a进行一些操作
return a;
}
}
@override意味着B对父类A中的get方法进行了重写,但是依上面的程序来看,只是重载,依然可以执行父类的方法,这和期望是不附的,也不符合java继承、多态的特性。
注:子类重写父类方法,只要此实例的实际类型是子类类型了(不是引用类型),便不能调用父类的方法,这是语言的多态特性。
为了解决这个问题,java在编译期间加入了桥接方法。编译后再翻译为java原文件,应如下所示:
public class A{
public Object get(Object a){
//a进行一些操作
return a;
}
}
public class B extends A{
@override
public String get(String a){
//a进行一些操作
return a;
}
public Object get(Object a){
return get((String)a)
}
}
类B中的第二个方法,即为桥接方法。桥接方法重写了父类相同的方法,并且桥接方法中,最终调用了期望的重写方法,并且桥接方法在调用目的方法时,参数被强制转换为指定的泛型类型。
桥接方法测试:
A a=new B();
a.get(new Object());
由于A未声明泛型类型,编译期间会警告存在类型转换错误。
运行期间,还真抛出了类型转换错误,Object to String。
实例a实际类型为B,在调用a.get方法时最终调用了B类中的桥接方法,保证了传入参数为子类继承父类使用的泛型类型。
初识Java泛型以及桥接方法的更多相关文章
- Java系列:关于Java中的桥接方法
这两天在看<Java核心技术 卷1>的泛型相关章节,其中说到了在泛型子类中override父类的泛型方法时,编译器会自动生成一个桥接方法,这块有点看不明白. 书上的例子代码如下: publ ...
- java中的桥接方法
本文转载自java中什么是bridge method(桥接方法) 导语 在看spring-mvc的源码的时候,看到在解析handler方法时,有关于获取桥接方法代码,不明白什么是桥接方法,经过查找资料 ...
- 使用java泛型设计通用方法
泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 因此我们可以利用泛型和反射来设计一些通用方法. 现在有2张表, 一张user表和一张stu ...
- 初识java泛型
1 协变数组类型(covariant array type) 数组的协变性: if A IS-A B then A[] IS-A B[] 也就是说,java中的数组兼容,一个类型的数组兼容他的子类类型 ...
- 3分钟快速搞懂Java的桥接方法
什么是桥接方法? Java中的桥接方法(Bridge Method)是一种为了实现某些Java语言特性而由编译器自动生成的方法. 我们可以通过Method类的isBridge方法来判断一个方法是否是桥 ...
- java 泛型的类型擦除与桥方法
泛型类 --代码参考:java核心技术 卷1 第十版 public class Pair<T> { private T first; private T second; //构造器 pub ...
- java泛型基础
泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 这种参数类型可以用在类.接口和方法的创建中, 分别称为泛型类.泛型接口.泛型方法. Ja ...
- Java泛型-内部原理: 类型擦除以及类型擦除带来的问题
一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...
- java泛型 8 泛型的内部原理:类型擦除以及类型擦除带来的问题
参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...
随机推荐
- 【Python基础学习三】数字(Number)
Python Number 数据类型用于存储数值. 数据类型是不允许改变的,这就意味着如果改变 Number 数据类型的值,将重新分配内存空间. Python 四种数值类型: 整型(Int) - 通常 ...
- Java 计算数学表达式(字符串解析求值工具)
Java字符串转换成算术表达式计算并输出结果,通过这个工具可以直接对字符串形式的算术表达式进行运算,并且使用非常简单. 这个工具中包含两个类 Calculator 和 ArithHelper Calc ...
- MysqlWorkbench连接远程数据
- socket通信之eofexception
方案一: 用read()将不会抛出异常.因为read是block方式的. readInt()抛出异常的原因(我遇到的)是对方在该端口没有消息发送. 方案二: socket的端口被阻塞了 ,也就是我们需 ...
- FileReader:读取本地图片文件并显示
最近忙得比狗还惨,导致长时间没能更新文章,真心对不住啊.抽空整理了下关于在页面上读取和显示本地图片的实例文章,本文通过实例讲解如何使用支持FileReader浏览器的用户将能够通过一个file inp ...
- iOS之九宫格图片
照片 现在人们的生活越来越丰富了,很多美好的瞬间都定格在一张张色彩绚丽的照片上,或许把照片珍藏在相册里,或许通过社交软件分享给亲朋好友.那社交软件上的照片是以什么形式展现的呢?那么接下来就要说到九宫格 ...
- Spring中文文档
前一段时间翻译了Jetty的一部分文档,感觉对阅读英文没有大的提高(*^-^*),毕竟Jetty的受众面还是比较小的,而且翻译过程中发现Jetty的文档写的不是很好,所以呢翻译的兴趣慢慢就不大了,只能 ...
- 使用FP-Growth算法高效发现频繁项集【zz】
FP树构造 FP Growth算法利用了巧妙的数据结构,大大降低了Aproir挖掘算法的代价,他不需要不断得生成候选项目队列和不断得扫描整个数据库进行比对.为了达到这样的效果,它采用了一种简洁的数据结 ...
- java中定时器的四种方法
package com.lid; import java.util.Calendar; import java.util.Date; import java.util.Timer; import ja ...
- MIT 6.828 JOS学习笔记9. Exercise 1.5
Lab 1 Exercise 5 再一次追踪一下boot loader的一开始的几句指令,找到第一条满足如下条件的指令处: 当我修改了boot loader的链接地址,这个指令就会出现错误. 找到这样 ...