- 浏览: 233193 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (102)
- 开源软件 (1)
- 并发 (14)
- WEB (1)
- NIO (4)
- Socket (5)
- 应用服务器 (4)
- 集群 (0)
- 数据库 (1)
- JAVA基础 (17)
- 开源框架 (2)
- 业务知识 (1)
- JVM (9)
- Windows (1)
- LINUX (0)
- Jquery (0)
- JMS (0)
- Cache (0)
- Oracle (5)
- XML (0)
- EJB (0)
- WebService (0)
- Struts2 (1)
- Hibernate (1)
- Spring (0)
- 设计模式 (4)
- UML (0)
- JS (12)
- 网络爬虫 (0)
- 数据结构与算法 (1)
- EXT (1)
- DIV+CSS (2)
- 安全 (3)
- Android (9)
- LDAP (1)
- Mybatis (1)
最新评论
-
Dom_4j:
...
理解注解中的@Inherited -
s469799470:
demo少个ID
iframe父子页面交互问题 -
errorerror0:
...
iframe父子页面交互问题 -
errorerror0:
iframe父子页面交互问题 -
johnawm:
2012-12-18 wangshibei 写道CountD ...
CountDownLatch的使用
泛型类型的擦除:
说明泛型类型String和Integer都被擦除掉了,只剩下了原始类型。
试想在什么时候进行擦除的呢?是不是编译后进行擦除的?
运行结果可以看到,容器已经有1和asd,说明经过反射之后,可以往arrayList3放入字符串,
说明了Integer泛型实例在编译之后被擦除了,只保留了原始类型。
经过泛型擦除后的原始类型为:
类型擦除引起的问题:
Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型
先检查,在编译,以及检查编译的对象和引用传递的问题
既然说类型变量会在编译的时候擦除掉,那为什么我们往 arrayList中,不能使用add方法添加整形呢?不是说泛型变量Integer会在编译时候擦除变为原始类型Object吗,为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的。
通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
自动类型转换:
因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。,既然都被替换为原始类型,那么为什么我们在获取的时候,
不需要进行强制类型转换呢?
然后反编了下字节码,如下
看第22 ,它调用的是ArrayList.get()方法,方法返回值是Object,说明类型擦除了。然后第25,它做了一个checkcast操作,即检查类型#19, 在在上面找#19引用的类型,他是
9: new #19 // class java/util/Date
是一个Date类型,即做Date类型的强转。
所以不是在get方法里强转的,是在你调用的地方强转的。
类型擦除与多态的冲突和解决方法:
然后我们想要一个子类继承它
在这个子类中,我们设定父类的泛型类型为Pair<Date>,在子类中,我们覆盖了父类的两个方法
分析:
实际上,类型擦除后,父类的的泛型类型全部变为了原始类型Object,所以父类编译之后会变成下面的样子:
父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。
我们在一个main方法测试一下:
如果是重载不会出现编译错误,说明是重写了。
为什么会是这样呢?
虚拟机并不能将泛型类型变为Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。
JVM知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!如果真的不能的话,那我们怎么去重写我们想要的Date类型参数的方法啊。
于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法。
class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {
com.tao.test.DateInter();
Code:
0: aload_0
1: invokespecial #8 // Method com/tao/test/Pair."<init>"
:()V
4: return
public void setValue(java.util.Date); //我们重写的setValue方法
Code:
0: aload_0
1: aload_1
2: invokespecial #16 // Method com/tao/test/Pair.setValue
:(Ljava/lang/Object;)V
5: return
public java.util.Date getValue(); //我们重写的getValue方法
Code:
0: aload_0
1: invokespecial #23 // Method com/tao/test/Pair.getValue
:()Ljava/lang/Object;
4: checkcast #26 // class java/util/Date
7: areturn
public java.lang.Object getValue(); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法
;
4: areturn
public void setValue(java.lang.Object); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: aload_1
2: checkcast #26 // class java/util/Date
5: invokevirtual #30 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法
)V
8: return
}
从编译的结果来看,我们本意重写setValue和getValue方法的子类,竟然有4个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的桥方法。
可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。
而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
所以,虚拟机巧妙的使用了巧方法,来解决了类型擦除和多态的冲突。
还有一点疑问:子类中的巧方法 Object getValue()和Date getValue()是同 时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。
如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,
所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。
ArrayList<String> arrayList1=new ArrayList<String>(); ArrayList<Integer> arrayList2=new ArrayList<Integer>(); System.out.println(arrayList1.getClass()==arrayList2.getClass());//结果为true
说明泛型类型String和Integer都被擦除掉了,只剩下了原始类型。
试想在什么时候进行擦除的呢?是不是编译后进行擦除的?
ArrayList<Integer> arrayList3=new ArrayList<Integer>(); arrayList3.add(1);//这样调用add方法只能存储整形,因为泛型类型的实例为Integer arrayList3.getClass().getMethod("add", Object.class).invoke(arrayList3, "asd"); for (int i=0;i<arrayList3.size();i++) { System.out.println(arrayList3.get(i)); }
运行结果可以看到,容器已经有1和asd,说明经过反射之后,可以往arrayList3放入字符串,
说明了Integer泛型实例在编译之后被擦除了,只保留了原始类型。
class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
经过泛型擦除后的原始类型为:
class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
类型擦除引起的问题:
Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型
先检查,在编译,以及检查编译的对象和引用传递的问题
既然说类型变量会在编译的时候擦除掉,那为什么我们往 arrayList中,不能使用add方法添加整形呢?不是说泛型变量Integer会在编译时候擦除变为原始类型Object吗,为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的。
public static void main(String[] args) { ArrayList<String> arrayList=new ArrayList<String>(); arrayList.add("123"); arrayList.add(123);//编译错误 }
public static void main(String[] args) { // ArrayList<String> arrayList1=new ArrayList(); arrayList1.add("1");//编译通过 arrayList1.add(1);//编译错误 String str1=arrayList1.get(0);//返回类型就是String ArrayList arrayList2=new ArrayList<String>(); arrayList2.add("1");//编译通过 arrayList2.add(1);//编译通过 Object object=arrayList2.get(0);//返回类型就是Object new ArrayList<String>().add("11");//编译通过 new ArrayList<String>().add(22);//编译错误 String string=new ArrayList<String>().get(0);//返回类型就是String }
通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
自动类型转换:
因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。,既然都被替换为原始类型,那么为什么我们在获取的时候,
不需要进行强制类型转换呢?
public class Test { public static void main(String[] args) { ArrayList<Date> list=new ArrayList<Date>(); list.add(new Date()); Date myDate=list.get(0); }
然后反编了下字节码,如下
public static void main(java.lang.String[]); Code: 0: new #16 // class java/util/ArrayList 3: dup 4: invokespecial #18 // Method java/util/ArrayList."<init :()V 7: astore_1 8: aload_1 9: new #19 // class java/util/Date 12: dup 13: invokespecial #21 // Method java/util/Date."<init>":() 16: invokevirtual #22 // Method java/util/ArrayList.add:(L va/lang/Object;)Z 19: pop 20: aload_1 21: iconst_0 22: invokevirtual #26 // Method java/util/ArrayList.get:(I java/lang/Object; 25: checkcast #19 // class java/util/Date 28: astore_2 29: return
看第22 ,它调用的是ArrayList.get()方法,方法返回值是Object,说明类型擦除了。然后第25,它做了一个checkcast操作,即检查类型#19, 在在上面找#19引用的类型,他是
9: new #19 // class java/util/Date
是一个Date类型,即做Date类型的强转。
所以不是在get方法里强转的,是在你调用的地方强转的。
类型擦除与多态的冲突和解决方法:
class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
然后我们想要一个子类继承它
class DateInter extends Pair<Date> { @Override public void setValue(Date value) { super.setValue(value); } @Override public Date getValue() { return super.getValue(); } }
在这个子类中,我们设定父类的泛型类型为Pair<Date>,在子类中,我们覆盖了父类的两个方法
分析:
实际上,类型擦除后,父类的的泛型类型全部变为了原始类型Object,所以父类编译之后会变成下面的样子:
class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。
我们在一个main方法测试一下:
public static void main(String[] args) throws ClassNotFoundException { DateInter dateInter=new DateInter(); dateInter.setValue(new Date()); dateInter.setValue(new Object());//编译错误 }
如果是重载不会出现编译错误,说明是重写了。
为什么会是这样呢?
虚拟机并不能将泛型类型变为Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。这样,类型擦除就和多态有了冲突。
JVM知道你的本意吗?知道!!!可是它能直接实现吗,不能!!!如果真的不能的话,那我们怎么去重写我们想要的Date类型参数的方法啊。
于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法。
class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {
com.tao.test.DateInter();
Code:
0: aload_0
1: invokespecial #8 // Method com/tao/test/Pair."<init>"
:()V
4: return
public void setValue(java.util.Date); //我们重写的setValue方法
Code:
0: aload_0
1: aload_1
2: invokespecial #16 // Method com/tao/test/Pair.setValue
:(Ljava/lang/Object;)V
5: return
public java.util.Date getValue(); //我们重写的getValue方法
Code:
0: aload_0
1: invokespecial #23 // Method com/tao/test/Pair.getValue
:()Ljava/lang/Object;
4: checkcast #26 // class java/util/Date
7: areturn
public java.lang.Object getValue(); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法
;
4: areturn
public void setValue(java.lang.Object); //编译时由编译器生成的巧方法
Code:
0: aload_0
1: aload_1
2: checkcast #26 // class java/util/Date
5: invokevirtual #30 // Method setValue:(Ljava/util/Date; 去调用我们重写的setValue方法
)V
8: return
}
从编译的结果来看,我们本意重写setValue和getValue方法的子类,竟然有4个方法,其实不用惊奇,最后的两个方法,就是编译器自己生成的桥方法。
可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。
而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
所以,虚拟机巧妙的使用了巧方法,来解决了类型擦除和多态的冲突。
还有一点疑问:子类中的巧方法 Object getValue()和Date getValue()是同 时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。
如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,
所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。
发表评论
-
深入学习EnumSet
2018-03-25 00:18 527Set接口的实现类HashSet/TreeSet,它们内部都是 ... -
枚举中valueOf用法
2018-01-14 11:21 4534Enum的特征如下: 1.它不能有public的构造函数,这样 ... -
mybatis源码学习总结-class.getResource方法与claasloader.getResource方法的区别
2018-01-14 10:49 1047Class.getResources(String path) ... -
使用自定义注解搭建简单框架
2017-05-01 00:54 542本文主要介绍如何使用Java运行时级别的注解配合反射来搭建框架 ... -
java注解处理器
2017-04-30 17:43 675注解处理器: Java SE5扩 ... -
理解注解中的@Inherited
2017-04-30 16:06 32415@Inherited: @Inherited 元注解是一 ... -
JAVA注解总结
2017-04-30 12:59 584元注解: 元注解 ... -
java泛型理解2
2017-01-07 22:54 486泛型类型注意细节: 1.泛型类型变量不能是基本数据类型 比如, ... -
逻辑运算与移位运算
2012-11-27 14:56 1288源码:正数的补码与原码相同例+7 源码:00000111 补码 ... -
关于数字签名基础知识
2012-10-08 17:40 13391.消息摘要 public class MessageDige ... -
JJd
2012-05-10 20:02 0//Access-Request报文 创建message ... -
HashMap原理
2012-04-29 17:27 1816概述: HashMap是基于哈希表的Map接口的非同步实现。此 ... -
使用内部类有什么好处
2012-03-17 12:41 1333使用内部类在java编程高级设计中是必须的,它能使你的代码更加 ... -
关于a& 0xff的运算
2011-11-21 11:23 1445byte是一个有符号数可以表示-128~+127,但是作为一个 ... -
java调用Windows命令行
2011-11-20 21:32 1740java来调用windows的命令,一般情况下下面两行代码即可 ... -
parseInt(String s, int radix)用法介绍
2011-11-19 22:13 6324parseInt(String s, int radix) , ... -
深入理解String.getBytes()中编码问题
2011-11-04 15:25 2561查看jdk的源码得知,String.getBytes()的源码 ...
相关推荐
Java 泛型详解 Java 泛型是 Java SE 5.0 中引入的一项特征,它允许程序员在编译时检查类型安全,从而减少了 runtime 错误的可能性。泛型的主要优点是可以Reusable Code,让程序员编写更加灵活和可维护的代码。 ...
Java泛型是Java编程语言中的一个强大特性,它允许我们在定义类、接口和方法时指定类型参数,从而实现代码的重用和类型安全。在Java泛型应用实例中,我们可以看到泛型如何帮助我们提高代码的灵活性和效率,减少运行时...
Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...
下面我们将详细探讨Java泛型接口的相关知识点。 1. **泛型接口的定义** 泛型接口的定义方式与普通接口类似,只是在接口名之后添加了尖括号`<T>`,其中`T`是一个类型参数,代表某种未知的数据类型。例如: ```java...
下面我们将深入探讨Java泛型方法的概念、语法以及使用示例。 **一、泛型方法概念** 泛型方法是一种具有类型参数的方法,这些类型参数可以在方法声明时指定,并在方法体内部使用。与类的泛型类似,它们提供了编译时...
在Java编程语言中,泛型(Generics)是一种强大的特性,它允许我们在编写代码时指定容器(如集合)可以存储的数据类型。这提高了代码的安全性和效率...通过学习和理解这些示例,你可以更好地掌握Java泛型类的类型识别。
通过学习这些知识点,开发者能更好地理解Java泛型的内部原理,以及如何在实际项目中充分利用泛型的特性,提高代码质量和安全性。在这个视频教程中,张孝祥老师将详细讲解这些概念,并通过实例帮助学员深入掌握Java...
SUN公司的Java泛型编程文档,包括英文原版和网络翻译版,为开发者提供了深入理解和熟练运用泛型的重要资源。 首先,泛型的基本概念是允许在定义类、接口和方法时使用类型参数,这样就可以在编译时检查类型安全,...
### JVM如何理解Java泛型类 #### 一、引言 在Java中,泛型是一种强大的功能,它允许程序员编写灵活且类型安全的代码。然而,对于Java虚拟机(JVM)来说,它实际上并不理解泛型的概念。所有的泛型信息在编译阶段就被...
总之,深入理解Java泛型能够帮助开发者编写更安全、更健壮的代码。通过掌握泛型的原理和细节,开发者可以有效利用泛型来优化代码设计,提升程序的类型安全,同时在维持程序性能的同时简化代码。
通过理解并熟练运用Java泛型,开发者可以编写出更加健壮、类型安全且易于维护的代码,提升软件质量。Java泛型技术的不断发展和完善,也为Java程序员提供了强大的工具来应对复杂的数据处理场景。
Java 泛型是一种强大的工具,它允许我们在编程时指定变量的类型,提供了编译时的类型安全。然而,Java 的泛型在运行时是被擦除的,这意味着在运行...理解并掌握这些设计模式对于编写健壮、可维护的Java代码至关重要。
本文将深入探讨C#和Java在泛型实现上的异同,帮助开发者更好地理解和利用这两种语言的泛型功能。 首先,我们来看C#中的泛型。C#自2.0版本开始引入泛型,它允许开发者在类、接口和方法中定义类型参数,从而创建可...
Java泛型是Java编程语言中的一个关键特性,它在2004年随着JDK 5.0的发布被引入。泛型的主要目的是提供类型安全,帮助程序员在编译时发现...在这个"java泛型Demo"中,你将有机会实践这些概念,加深对Java泛型的理解。
Java泛型技术的发展历程及其在JDK1.4中的实现,是IT领域特别是软件开发与设计中的一个重要里程碑。本文将深入探讨泛型技术的概念、历史背景、与其它编程概念的区别,以及其在Java语言中的具体应用。 ### 泛型技术...