- 浏览: 898059 次
- 性别:
- 来自: 武汉
文章分类
最新评论
-
小宇宙_WZY:
膜拜一下大神,解决了我一个大问题,非常感谢 orz
【解惑】深入jar包:从jar包中读取资源文件 -
JKL852qaz:
感谢,遇到相同的问题!
【解惑】深入jar包:从jar包中读取资源文件 -
lgh1992314:
为什么java中调用final方法是用invokevirtua ...
【解惑】Java动态绑定机制的内幕 -
鲁曼1991:
说的都有道理,protected只能被同一级包的类所调用
【解惑】真正理解了protected的作用范围 -
鲁曼1991:
...
【总结】String in Java
//泛型代码 public class Pair<T>{ private T first=null; private T second=null; public Pair(T fir,T sec){ this.first=fir; this.second=sec; } public T getFirst(){ return this.first; } public T getSecond(){ return this.second; } public void setFirst(T fir){ this.first=fir; } }
上面是一个很典型的泛型(generic)代码。T是类型变量,可以是任何引用类型。
1、Generic class 创建对象
Pair<String> pair1=new Pair("string",1); ...①
Pair<String> pair2=new Pair<String>("string",1) ...②
有个很有趣的现象:
①代码在编译期不会出错,②代码在编译期会检查出错误。
这个问题其实很简单
(1) JVM本身并没有泛型对象这样的一个特殊概念。所有的泛型类对象在编译器会全部变成普通类对象(这一点会在下面详细阐述)。
比如①,②两个代码编译器全部调用的是 Pair(Object fir, Object sec)这样的构造器。
因此代码①中的new Pair("string",1)在编译器是没有问题的,毕竟编译器并不知道你创建的Pair类型中具体是哪一个类型变量T,而且编译器肯定了String对象和Integer对象都属于Object类型的。
但是一段运行pair1.getSecond()就会抛出ClassCastException异常。这是因为JVM会根据第一个参数"string"推算出T类型变量是String类型,这样getSecond也应该是返回String类型,然后编译器已经默认了second的操作数是一个值为1的Integer类型。当然就不符合JVM的运行要求了,不终止程序才怪。
(2) 但代码②会在编译器报错,是因为new Pair<String>("string",1)已经指明了创建对象pair2的类型变量T应该是String的。所以在编译期编译器就知道错误出在第二个参数Integer了。
小结一下:
创建泛型对象的时候,一定要指出类型变量T的具体类型。争取让编译器检查出错误,而不是留给JVM运行的时候抛出异常。
2、JVM如何理解泛型概念
—— 类型擦除
事实上,JVM并不知道泛型,所有的泛型在编译阶段就已经被处理成了普通类和方法。
处理方法很简单,我们叫做类型变量T的擦除(erased)
。
无论我们如何定义一个泛型类型,相应的都会有一个原始类型被自动提供。原始类型的名字就是擦除类型参数的泛型类型的名字。
如果泛型类型的类型变量没有限定(<T>)
,那么我们就用Object作为原始类型;
如果有限定(<T extends XClass>),我们就XClass作为原始类型;
如果有多个限定(<T extends XClass1&XClass2>),我们就用第一个边界的类型变量XClass1类作为原始类型;
比如上面的Pair<T>例子,编译器会把它当成被Object原始类型替代的普通类来替代。
//编译阶段:类型变量的擦除 public class Pair{ private Object first=null; private Object second=null; public Pair(Object fir,Object sec){ this.first=fir; this.second=sec; } public Object getFirst(){ return this.first; } public void setFirst(Object fir){ this.first=fir; } }
3、泛型约束和局限性—— 类型擦除所带来的麻烦
(1) 继承泛型类型的多态麻烦。(—— 子类没有覆盖住父类的方法 )
看看下面这个类SonPair
class SonPair extends Pair<String>{ public void setFirst(String fir){....} }
很明显,程序员的本意是想在SonPair类中覆盖父类Pair<String>的setFirst(T fir)这个方法。但事实上,SonPair中的setFirst(String fir)方法根本没有覆盖住Pair<String>中的这个方法。
原因很简单,Pair<String>在编译阶段已经被类型擦除为Pair了,它的setFirst方法变成了setFirst(Object fir)。
那么SonPair中
setFirst(String)当然无法覆盖住父类的setFirst(Object)了。
这对于多态来说确实是个不小的麻烦,我们看看编译器是如何解决这个问题的。
编译器
会自动在
SonPair中生成一个桥方法(bridge method
)
:
public void setFirst(Object fir){
setFirst((String) fir)
}
这样,SonPair的桥方法确实能够覆盖泛型父类的setFirst(Object)
了。而且桥方法内部其实调用的是子类字节setFirst(String)方法。对于多态来说就没问题了。
问题还没有完,多态中的方法覆盖是可以了,但是桥方法却带来了一个疑问:
现在,假设 我们还想在 SonPair 中覆盖getFirst()方法呢?
class SonPair extends Pair<String>{ public String getFirst(){....} }
由于需要桥方法来覆盖父类中的getFirst,编译器会自动在SonPair中生成一个 public Object getFirst()桥方法。
但是,疑问来了,SonPair中出现了两个方法签名一样的方法(只是返回类型不同):
①String getFirst() // 自己定义的方法
②Object getFirst() // 编译器生成的桥方法
难道,编译器允许出现方法签名相同的多个方法存在于一个类中吗?
事实上有一个知识点可能大家都不知道:
① 方法签名
确实只有方法名+参数列表
。这毫无疑问!
② 我们绝对不能编写出方法签名一样的多个方法
。如果这样写程序,编译器是不会放过的。这也毫无疑问!
③ 最重要的一点是:JVM会用参数类型和返回类型来确定一个方法。
一旦编译器通过某种方式自己编译出方法签名一样的两个方法(只能编译器自己来创造这种奇迹,我们程序员却不能人为的编写这种代码)。JVM还是能够分清楚这些方法的,前提是需要返回类型不一样。
(2) 泛型类型中的方法冲突
还是来看一段代码:
//在上面代码中加入equals方法 public class Pair<T>{ public boolean equals(T value){ return (first.equals(value)); } }
这样看似乎没有问题的代码连编译器都通过不了:
【Error】 Name clash: The method equals(T) of type Pair<T> has the same erasure as equals(Object) of type Object but does not override it。
编译器说你的方法与Object中的方法冲突了。这是为什么?
开始我也不太明白这个问题,觉得好像编译器帮助我们使得equals(T)这样的方法覆盖上了Object中的equals(Object)。经过大家的讨论,我觉得应该这么解释这个问题?
首先、我们都知道子类方法要覆盖,必须与父类方法具有相同的方法签名(方法名+参数列表)。而且必须保证子类的访问权限>=父类的访问权限。这是大家都知道的事实。
然后、在上面的代码中,当编译器看到Pair<T>中的equals(T)方法时,第一反应当然是equals(T)没有覆盖住父类Object中的equals(Object)了。
接着、编译器将泛型代码中的T用Object替代(擦除)。突然发现擦除以后equals(T)变成了equals(Object),糟糕了,这个方法与Object类中的equals一样了。基于开始确定没有覆盖这样一个想法,编译器彻底的疯了(精神分裂)。然后得出两个结论:①坚持原来的思想:没有覆盖。但现在一样造成了方法冲突了。 ②写这程序的程序员疯了(哈哈)。
再说了,拿Pair<T>对象和T对象比较equals,就像牛头对比马嘴,哈哈,逻辑上也不通呀。
(3) 没有泛型数组一说
Pair<String>[] stringPairs=new Pair<String>[10];
Pair<Integer>[] intPairs=new Pair<Integer>[10];
这种写法编译器会指定一个Cannot create a generic array of Pair<String>的错误
我们说过泛型擦除之后,Pair<String>[]会变成Pair[],进而又可以转换为Object[];
假设泛型数组存在,那么
Object[0]=stringPairs[0]; Ok
Object[1]=intPairs[0]; Ok
这就麻烦了,理论上将Object[]可以存储所有Pair对象,但这些Pair对象是泛型对象,他们的类型变量都不一样,那么调用每一个Object[]数组元素的对象方法可能都会得到不同的记过,也许是个字符串,也许是整形,这对于JVM可是无法预料的。
记住: 数组必须牢记它的元素类型,也就是所有的元素对象都必须一个样,泛型类型恰恰做不到这一点。即使Pair<String>,Pair<Integer>... 都是Pair类型的,但他们还是不一样。
总结:泛型代码与JVM
① 虚拟机中没有泛型,只有普通类和方法。
② 在编译阶段,所有泛型类的类型参数都会被Object或者它们的限定边界来替换。(类型擦除)
③ 在继承泛型类型的时候,桥方法的合成是为了避免类型变量擦除所带来的多态灾难。
评论
此处应该是笔误 在最后也提到是Java虚拟机中不存在泛型概念
换言之 运行态没有泛型,泛型只存在于编译期
楼主的文章用词还需要斟酌 否则容易误导别人
另外,编译时也不是完全当作原始类型处理,比较一下泛型生成的class和原始类型生成的class大小就知道了.
编译器定义方法签名是 方法名+参数列表
所以我们不能写出只有返回类型不同的方法
但是虚拟机中方法签名是 返回类型+方法名+参数列表,虚拟机知道如何定位方法.
关于最后一个错误,因为equals比的是this,所以equals(Pair<T>) 就ok了。
public class Pair<T> { public boolean equals(Pair<T> value) { return (first.equals(value) && second.equals(value)); } }
当然, 这里你指定了比较的确切类型, 但是正常的equals应该覆盖Object类, 你这个写法没什么意义, 因为equals使用的环境一般无法确定比较的对象的类型
不就表示擦除后的T变成object了,和Object类的equals(Object)方法是一样的,没有进行重写呀
还有其它玄机吗?
LZ的意思是 既然一样了不就能当成重写了嘛, 干嘛还报错
不就表示擦除后的T变成object了,和Object类的equals(Object)方法是一样的,没有进行重写呀
还有其它玄机吗?
类Text
a,b 都继承Text
LIST<Text> text;
text.ADD(a);
text.ADD(b);
这个时候jvm怎么处理我这个泛型
将a,b都向上转型成Text
Name clash: The method equals(T) of type TT<T> has the same erasure as equals(Object) of type Object but does not override it
有什么看不懂的
equals(Object)和equals(T)擦除后是一样的
你自己前面也说到了 这里的T对于编译器来说就是Object
如果对编译器来说是一样,那么为什么不算是override?
不要光看提示信息,就想当然。
不能说对编译器来是完全一样, 应该说编译后是完全一样, 而编译时, 编译器会验证重写的一些判定
在这里, 编译器的工作大概有几步:
1. 编译器首先根据方法签名判断子类的equals方法是个新的方法(因为你使用的T作为参数), 与父类Object的equals方法并不产生重写关系
2. 编译器进行泛型擦除
3. 编译器发现擦除之后出现了两个完全一致的equals(Object)方法, 而子类的equal方法是擦除产生的和定义时的equals(T)对应, 这时编译器不知道你到底是想重写还是在定义一个新的equals
4. 可以预期的是, 如果编译器放过这段代码, 那么在调用equals方法时, jvm无法知道到底是在调用哪一个equals方法(因为同时存在两个equals方法, 而却不是重写关系)
所以编译器为确保代码安全, 会报错:
名称冲突:类型 Test<T> 的方法 equals(T)与类型 Object 的 equals(Object)具有相同的擦除,但是未覆盖它
类Text
a,b 都继承Text
LIST<Text> text;
text.ADD(a);
text.ADD(b);
这个时候jvm怎么处理我这个泛型
<div class="quote_div">
<p> </p>
<p><strong><span style="color: #800000;">3、泛型约束和局限性—— 类型擦除所带来的麻烦</span>
</strong></p>
<p><strong><span style="color: #800000;">(1) 继承泛型类型的多态麻烦。(—— 子类没有覆盖住父类的方法 )<br></span>
</strong>
</p>
<p><strong><span style="color: #800000;"> </span>
</strong>
</p>
<p> </p>
<p> ..................</p>
<p><span style="color: #ff0000;"> 编译器</span>
<span style="color: #ff0000;"><strong>会自动在</strong>
<strong><span>SonPair中生成一个桥方法(bridge method</span>
)</strong>
:</span>
<span style="color: #ff0000;"><br>
public void setSecond(Object sec){<br>
setSecond((String) sec)<br>
}</span>
</p>
</div>
<p> </p>
<p> </p>
<p> 红色部分的方法应该是setFirst吧?</p>
<p> </p>
<p> 呵呵,总体写的挺好的!</p>
<div class="quote_div">
<p> <br>
<span style="color: #ff0000;">小结一下:</span>
<br>
<span style="color: #ff0000;">创建泛型对象的时候,一定要指出类型变量T的具体类型。争取让编译器检查出错误,而不是留给JVM运行的时候抛出异常。</span>
</p>
<p> </p>
</div>
<p> </p>
<p> 在new的时候加上具体类型的方式,在JAVA7里有新的改进:</p>
<p> </p>
<p><strong> <br></strong>
</p>
<pre name="code" class="java"> 1. Map<String, List<String>> anagrams = new HashMap<String, List<String>>(); </pre>
<p> <span style="white-space: pre;">becomes:</span>
</p>
<div class="dp-highlighter">
<pre name="code" class="java">Map<String, List<String>> anagrams = new HashMap<>(); </pre>
</div>
<div class="dp-highlighter"> </div>
<div class="dp-highlighter"> 估计做法跟LZ说的相同,不加<>一样会出错。。。<br>
</div>
请问楼主,这些知识是平时积累的吗?
还是有什么地方对这些有比较深刻的讲解的?
没事喜欢看些Java方面的书,最近在看Core Java和深入JVM,有新体会就拿上来和大家分享。即增加自己学习的兴趣,又能够和大家交流,有的时候还能够满足一下自己虚荣的心态。哈哈....
钻研精神+分享心理=大师,中国真正的大师太少了。
Name clash: The method equals(T) of type TT<T> has the same erasure as equals(Object) of type Object but does not override it
有什么看不懂的
equals(Object)和equals(T)擦除后是一样的
你自己前面也说到了 这里的T对于编译器来说就是Object
如果对编译器来说是一样,那么为什么不算是override?
不要光看提示信息,就想当然。
是我问题没有阐述清楚,我的意思是,这样Pair<T>中的equals方法不是正好override Object中的equals方法吗,为什么会有冲突,然后编译器说没有覆盖。不太明白??
Name clash: The method equals(T) of type TT<T> has the same erasure as equals(Object) of type Object but does not override it
有什么看不懂的
equals(Object)和equals(T)擦除后是一样的
你自己前面也说到了 这里的T对于编译器来说就是Object
我也很纳闷,擦除后是一样的,那么Pair<T>不是正好覆盖了Object中的equals方法吗,为什么会有冲突。
class A{
public void a(){}
}
class B extends A{
public void a(){}
}
B中的a()和A中的a()不也是一样的吗,为什么没有说冲突呢?
还希望牛人指点。谢谢大家
我也是啊
对泛型有了新的体会,不过有些概念还需要加深!
Name clash: The method equals(T) of type TT<T> has the same erasure as equals(Object) of type Object but does not override it
有什么看不懂的
equals(Object)和equals(T)擦除后是一样的
你自己前面也说到了 这里的T对于编译器来说就是Object
如果对编译器来说是一样,那么为什么不算是override?
不要光看提示信息,就想当然。
请问楼主,这些知识是平时积累的吗?
还是有什么地方对这些有比较深刻的讲解的?
发表评论
-
NIO
2010-08-05 10:36 0在JDK1.4以前,I/O输入输出处理,我们把它称为旧 ... -
【总结】Java线程同步机制深刻阐述
2010-05-16 10:21 6005全文转载:http://www.iteye ... -
【JDK优化】java.util.Arrays的排序研究
2010-05-12 21:06 9193作者题记:JDK中有很多算法具有优化的闪光点,值得好好研究。 ... -
【JDK优化】 Integer 自动打包机制的优化
2010-03-12 19:14 4204我们首先来看一段代码: Integer i=100; In ... -
【总结】Java与字符编码问题详谈
2009-12-30 09:11 9428一、字符集和字符编码方式 计算机只懂得0/1两种信号 ... -
【解惑】 正确理解线程等待和释放(wait/notify)
2009-12-29 13:40 19772对于初学者来说,下面这个例子是一个非常常见的错误。 /** ... -
【解惑】正确的理解this 和 super
2009-12-05 09:46 4474转载: 《无聊 ... -
【解惑】真正理解了protected的作用范围
2009-11-21 18:00 5095一提到访问控 ... -
【总结】String in Java
2009-11-21 17:52 10987作者:每次上网冲杯Java时,都能看到关于String无休无止 ... -
【解惑】真正理解了protected的作用范围
2009-11-16 17:11 585一提到访问控制符protected,即使是初学者 ... -
总结Java标准类库中类型相互转化的方法
2009-11-09 21:57 210组一: ☆ String → byte[ ... -
方法没覆盖住带来的烦恼
2009-11-05 09:18 100Object类是所有类的祖宗,它的equals方法比较的 ... -
【解惑】数组向上转型的陷阱
2009-11-03 11:44 1890问题提出: 有两个类Manager和Em ... -
【总结】java命令解析以及编译器,虚拟机如何定位类
2009-11-01 16:25 5824学Java有些日子了,一直都使用IDE来写程序。这 ... -
【解惑】剖析float型的内存存储和精度丢失问题
2009-10-26 15:10 16087问题提出:12.0f-11.9f=0.10 ... -
【解惑】领略内部类的“内部”
2009-10-19 15:38 3601内部类有两种情况: (1) 在类中定义一个类(私有内部类 ... -
【解惑】深入jar包:从jar包中读取资源文件
2009-10-08 21:13 65951我们常常在代码中读取一些资源文件(比如图片,音乐,文 ... -
【解惑】理解java枚举类型
2009-09-26 09:37 3427枚举类型是JDK5.0的新特征。Sun引进了一个全新的关键字e ... -
编写自己的equals方法
2009-09-20 14:18 129在我的《令人头疼的"相等"关 ... -
【解惑】Java类型间的转型
2009-09-11 16:03 5682★ 基本数据类型间的转换 1、Java要做到平台无关 ...
相关推荐
2. **类与对象**:理解类作为对象的蓝图,对象作为类的实例。学习如何创建和使用类,包括构造函数、成员变量和方法。 3. **封装**:学习如何通过访问修饰符来实现数据隐藏,理解public、private、protected等关键字...
理解类的构造器、继承、封装和多态性对于Java编程至关重要。 3. **异常处理**:Java的异常处理机制帮助程序员捕获和处理运行时错误。try-catch-finally语句块用于处理可能出现的异常,使程序更加健壮。 4. **集合...
8. **泛型**:Java泛型引入了类型安全,增强了代码的可读性和可维护性。书中会探讨泛型的基本概念,通配符,以及在设计API时如何有效地使用泛型。 9. **模块化系统**:Java 9引入了模块系统(Project Jigsaw),...
7. **JVM原理**:理解Java虚拟机(JVM)的工作原理,包括类加载机制、内存模型(堆、栈、方法区等)和JVM调优,有助于提升程序运行效率。 8. **泛型**:泛型引入了类型参数,提高了代码的类型安全性和重用性。了解...
《JAVA解惑》这本书主要针对Java编程中遇到的各种常见问题和困惑进行了解答,旨在帮助开发者深入理解Java语言,提高编程技巧。以下是一些关键的知识点解析: 1. **异常处理**:Java中的异常处理是通过try-catch-...
《JAVA解惑》是Java开发者领域的一本经典著作,它被广大编程爱好者称为Java四大名著之一,旨在解决初学者及有一定经验的开发者在学习和使用Java过程中遇到的各种困惑。这本书深入浅出地讲解了Java语言的核心概念和...
- **类与对象**:Java是面向对象的语言,理解类的定义、对象的创建以及它们之间的关系是基础。 - **封装、继承和多态**:这三个面向对象的特性是理解Java程序设计的关键。 - **访问修饰符**:public、private、...
15. **JVM优化**:了解JVM内存模型、类加载机制以及调优技巧,如JVM参数设置、垃圾收集器选择,能有效提升Java应用的性能。 以上只是“Java解惑”中可能涵盖的一部分知识点,实际内容可能还会包括异常层次结构、...
"JAVA解惑.rar"这个压缩包显然包含了一些关于Java编程的问题解答或者教程,可能是针对初学者或者有经验的开发者在解决Java编程问题时遇到困惑的解答集。 首先,Java的核心特性包括“一次编写,到处运行”(Write ...
6. **泛型**:Java泛型提供了类型安全的容器,但它们也有自己的限制,如类型擦除和边界问题。书中可能解析这些概念,帮助开发者写出更安全的代码。 7. **反射与注解**:反射允许在运行时检查和修改类的结构,而注解...
通过阅读《Java解惑(中文版)》,开发者不仅可以解决实际编程中遇到的问题,还能加深对Java语言本质的理解,提高编程效率,降低bug率。对于初学者和有一定经验的开发者来说,这都是一本值得珍藏的参考资料。书中还...
本文档“java解惑.doc”旨在帮助Java开发者解决他们在学习和实践过程中遇到的问题,深入理解Java的核心概念和技术。 1. **基础语法与数据类型** Java的基础语法包括变量声明、条件语句(if-else、switch-case)、...
5. **内存管理**:理解Java的垃圾回收机制,如何避免内存泄漏,以及JVM内存模型。 6. **多线程**:线程的基本概念,线程同步与通信(synchronized关键字、wait/notify、Thread.join()等),线程池的使用。 7. **IO...
13. **Java泛型**:理解泛型的引入和使用,以及泛型通配符和边界。 14. **枚举和注解**:介绍枚举类型的应用,以及注解的作用和自定义注解。 15. **Java 8新特性**:讲解Lambda表达式、函数式接口、Stream API等...
10. **JVM优化**:理解JVM的工作原理,如类加载、字节码执行、内存模型和垃圾回收策略,可以帮助优化应用程序性能,减少内存占用。 11. **并发编程**:Java并发库提供了一套丰富的工具,如ExecutorService、...
要能够创建和使用类,以及理解类的构造函数、方法重载和覆盖、访问修饰符(public、private、protected、默认)。 3. **异常处理**:理解如何使用try-catch-finally语句块来捕获和处理异常,以及不同的异常类层次...
本篇将基于“Java解惑”这一主题,详细探讨Java中的常见问题、易错点以及需要注意的细节。 1. **内存管理与垃圾回收** - Java的自动内存管理机制是通过垃圾回收(Garbage Collection, GC)来实现的。理解如何工作...
2. **类与对象**:Java是面向对象的语言,理解类的定义、对象的创建、封装、继承和多态性是核心概念。类是数据和操作数据的方法的集合,而对象则是类的实例。 3. **异常处理**:Java有强大的异常处理机制,通过try-...