初识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的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...
随机推荐
- 构建高可用ZooKeeper集群(转载)
ZooKeeper 是 Apache 的一个顶级项目,为分布式应用提供高效.高可用的分布式协调服务,提供了诸如数据发布/订阅.负载均衡.命名服务.分布式协调/通知和分布式锁等分布式基础服务.由于 Zo ...
- java中的集合
集合比数组的优势: 1.集合可以存任意类型的变量,长度是可变的 2.数组只能存同一类型的变量,长度是固定的 3.集合中只能存对象 List集合 : 接口 提供公有的方法 特有的方法: 1.List中 ...
- 51nod1228 序列求和(自然数幂和)
与UVA766 Sum of powers类似,见http://www.cnblogs.com/IMGavin/p/5948824.html 由于结果对MOD取模,使用逆元 #include<c ...
- [译]:Orchard入门——部件管理
原文链接:Managing Widgets 在Orchard中,部件是可以加入到当前当前主题任何位置或区域(如侧栏sidebar或底部区域footer)的UI块(如:HTML)或代码部分(如:内容部分 ...
- 注解:【无连接表的】Hibernate双向1->N关联
Person与Address关联:双向1->N,[无连接表的],推荐使用 #由N端控制关联关系 #对于指定了mappedBy属性的@OneToMany,@ManyToMany,@OneToOne ...
- UWP 下拉刷新控件(PullToRefreshControl)
最近项目里面有下拉刷新的需求,自己做了一个,效果还不错. <Style TargetType="local:PullToRefreshControl"> <Set ...
- 进击的Python【第一章】:Python背景初探与Python基础(一)
Python背景初探 一.Python起源 Python的创始人为Guido van Rossum.1989年圣诞节期间,在阿姆斯特丹,Guido为了打发圣诞节的无趣,决心开发一个新的脚本解释程序,做 ...
- alias实现命令别名
需要经常进入/srv/www/app/account这个目录,每次都得重复的输入这一长串路径进入该目录,显得麻烦而费时,因此可以将"cd /srv/www/app/account" ...
- Javascript中的apply与call详解
JavaScript中有一个call和apply方法,其作用基本相同,但也有略微的区别. 一.方法定义 1.call 方法 语法:call([thisObj[,arg1[, arg2[, [,.arg ...
- Only Link: Inheritance and the prototype chain
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_cha ...