JVM如何理解Java泛型类

来源:互联网 发布:杭州富谦网络 编辑:程序博客网 时间:2024/05/01 07:02




Java代码  收藏代码
  1. //泛型代码  
  2. public class Pair<T>{  
  3.        private T first=null;  
  4.        private T second=null;  
  5.   
  6.        public Pair(T fir,T sec){  
  7.             this.first=fir;  
  8.         this.second=sec;  
  9.        }  
  10.        public T getFirst(){  
  11.              return this.first;  
  12.        }  
  13.        public T getSecond(){  
  14.              return this.second;  
  15.        }  
  16.        public void setFirst(T fir){  
  17.          this.first=fir;  
  18.        }  
  19. }  

 上面是一个很典型的泛型(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原始类型替代的普通类来替代。

Java代码  收藏代码
  1. //编译阶段:类型变量的擦除  
  2. ublic class Pair{  
  3.        private Object first=null;  
  4.        private Object second=null;  
  5.   
  6.        public Pair(Object fir,Object sec){  
  7.            this.first=fir;  
  8.     this.second=sec;  
  9.        }  
  10.       public Object getFirst(){  
  11.             return this.first;  
  12.       }  
  13.       public void setFirst(Object fir){  
  14.      this.first=fir;  
  15.       }  
  16.    }  

 

3、泛型约束和局限性—— 类型擦除所带来的麻烦

(1)  继承泛型类型的多态麻烦。(—— 子类没有覆盖住父类的方法 )

     

     看看下面这个类SonPair

Java代码  收藏代码
  1. class SonPair extends Pair<String>{  
  2.           public void setFirst(String fir){....}  
  3. }  

     很明显,程序员的本意是想在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()方法呢?

Java代码  收藏代码
  1. class SonPair extends Pair<String>{  
  2.       public String getFirst(){....}  
  3. }  

      由于需要桥方法来覆盖父类中的getFirst,编译器会自动在SonPair中生成一个 public Object getFirst()桥方法。 
      但是,疑问来了,SonPair中出现了两个方法签名一样的方法(只是返回类型不同):

            ①String getFirst()   // 自己定义的方法

            ②Object getFirst()  //  编译器生成的桥方法 
      难道,编译器允许出现方法签名相同的多个方法存在于一个类中吗?

      事实上有一个知识点可能大家都不知道:
      ① 方法签名 确实只有方法名+参数列表 。这毫无疑问!
      ② 我们绝对不能编写出方法签名一样的多个方法 。如果这样写程序,编译器是不会放过的。这也毫无疑问!
      ③ 最重要的一点是:JVM会用参数类型和返回类型来确定一个方法。 一旦编译器通过某种方式自己编译出方法签名一样的两个方法(只能编译器自己来创造这种奇迹,我们程序员却不能人为的编写这种代码)。JVM还是能够分清楚这些方法的,前提是需要返回类型不一样。

 

(2) 泛型类型中的方法冲突

 

       还是来看一段代码:

Java代码  收藏代码
  1. //在上面代码中加入equals方法  
  2. public class Pair<T>{  
  3.       public boolean equals(T value){  
  4.             return (first.equals(value));  
  5.       }  
  6. }  

        这样看似乎没有问题的代码连编译器都通过不了:

       【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或者它们的限定边界来替换。(类型擦除)
    ③ 在继承泛型类型的时候,桥方法的合成是为了避免类型变量擦除所带来的多态灾难。

分享到:  
【解惑】 正确理解线程等待和释放(wait/ ... | 【解惑】正确的理解this 和 super
  • 2009-12-16 11:08
  • 浏览 4185
  • 评论(41)
  • 论坛回复 / 浏览 (36 / 10875)
  • 分类:编程语言
  • 相关推荐
评论
41 楼 zhenwodefengcai 2013-03-21  
40 楼 langshao 2010-01-19  
prowl 写道
msnvip 写道
Java代码  收藏代码
  1. public class TestFan {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      *  
  6.      * User是一个空class 
  7.      */  
  8.     public static void main(String[] args) {  
  9.         int i = 0;  
  10.         test(null);  
  11.     }  
  12.   
  13.     //  
  14.     //不添加此方法.编译,运行没问题.输出显示调用的user  
  15.     //添加此方法时候.编译期出错The method test(String) is ambiguous for the type TestFan  
  16.     public static void test(String str)  
  17.     {  
  18.         System.out.println("string");  
  19.     }  
  20.       
  21.       
  22.     public static void test(User users)  
  23.     {  
  24.         System.out.println("user");  
  25.     }  
  26.       
  27.     public static void test(Object users)  
  28.     {  
  29.         System.out.println("object");  
  30.     }  
  31. }  


请教下: 
1.没添加那个方法时候.为什么调用的会是user的那个方法? 
2.添加后为什么出错了



我觉得这可能是编译器的默认行为吧 默认
Java代码  收藏代码
  1. Object , ? extends Object   


类似多态? 

2个子类,编译器识别不了了。。。

Java代码  收藏代码
  1. public class Test {  
  2.     class A {  
  3.     }  
  4.   
  5.     class B extends A {  
  6.     }  
  7.   
  8.     public static void main(String[] args) {  
  9.         test(null);  
  10.     }  
  11.   
  12.     public static void test(A a) {  
  13.         System.out.println("A");  
  14.     }  
  15.   
  16.     public static void test(B b) {  
  17.         System.out.println("B");  
  18.     }  
  19. }  

结果,编译通过,输出B。 

Java代码  收藏代码
  1. public class Test {  
  2.     class A {  
  3.     }  
  4.   
  5.     class B {  
  6.     }  
  7.   
  8.     public static void main(String[] args) {  
  9.         test((B) null);  
  10.     }  
  11.   
  12.     public static void test(A a) {  
  13.         System.out.println("A");  
  14.     }  
  15.   
  16.     public static void test(B b) {  
  17.         System.out.println("B");  
  18.     }  
  19. }  

类A与B没有继承关系时,须将null强制转换。 

因此,如果有继承关系的方法重载,会尽可能调用子类那个方法。
39 楼 bugmenot 2010-01-14  
楼上的大大要看这个?
C代码  收藏代码
  1. JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))  
  2.   JVMWrapper("JVM_Clone");  
  3.   Handle obj(THREAD, JNIHandles::resolve_non_null(handle));  
  4.   const KlassHandle klass (THREAD, obj->klass());  
  5.   JvmtiVMObjectAllocEventCollector oam;  
  6.   
  7. #ifdef ASSERT  
  8.   // Just checking that the cloneable flag is set correct  
  9.   if (obj->is_javaArray()) {  
  10.     guarantee(klass->is_cloneable(), "all arrays are cloneable");  
  11.   } else {  
  12.     guarantee(obj->is_instance(), "should be instanceOop");  
  13.     bool cloneable = klass->is_subtype_of(SystemDictionary::cloneable_klass());  
  14.     guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");  
  15.   }  
  16. #endif  
  17.   
  18.   // Check if class of obj supports the Cloneable interface.  
  19.   // All arrays are considered to be cloneable (See JLS 20.1.5)  
  20.   if (!klass->is_cloneable()) {  
  21.     ResourceMark rm(THREAD);  
  22.     THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());  
  23.   }  
  24.   
  25.   // Make shallow object copy  
  26.   const int size = obj->size();  
  27.   oop new_obj = NULL;  
  28.   if (obj->is_javaArray()) {  
  29.     const int length = ((arrayOop)obj())->length();  
  30.     new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);  
  31.   } else {  
  32.     new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);  
  33.   }  
  34.   // 4839641 (4840070): We must do an oop-atomic copy, because if another thread  
  35.   // is modifying a reference field in the clonee, a non-oop-atomic copy might  
  36.   // be suspended in the middle of copying the pointer and end up with parts  
  37.   // of two different pointers in the field.  Subsequent dereferences will crash.  
  38.   // 4846409: an oop-copy of objects with long or double fields or arrays of same  
  39.   // won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead  
  40.   // of oops.  We know objects are aligned on a minimum of an jlong boundary.  
  41.   // The same is true of StubRoutines::object_copy and the various oop_copy  
  42.   // variants, and of the code generated by the inline_native_clone intrinsic.  
  43.   assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");  
  44.   Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,  
  45.                                (size_t)align_object_size(size) / HeapWordsPerLong);  
  46.   // Clear the header  
  47.   new_obj->init_mark();  
  48.   
  49.   // Store check (mark entire object and let gc sort it out)  
  50.   BarrierSet* bs = Universe::heap()->barrier_set();  
  51.   assert(bs->has_write_region_opt(), "Barrier set does not have write_region");  
  52.   bs->write_region(MemRegion((HeapWord*)new_obj, size));  
  53.   
  54.   // Caution: this involves a java upcall, so the clone should be  
  55.   // "gc-robust" by this stage.  
  56.   if (klass->has_finalizer()) {  
  57.     assert(obj->is_instance(), "should be instanceOop");  
  58.     new_obj = instanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL);  
  59.   }  
  60.   
  61.   return JNIHandles::make_local(env, oop(new_obj));  
  62. JVM_END  
38 楼 wgh-23 2009-12-20  
学习了,多谢!
37 楼 yidao620c 2009-12-18  
Heart.X.Raid 写道
mali0330 写道
我就想知道你是如何看到的JVM的源代码,可以和je分享一下么?谢谢


JDK 工具包里面都有一个src文件,里面是source code,但native方法,与虚拟机相关的部分没有公开。 

你知道怎么看Object中的clone方法的vative代码吗?我很想看,一直找不到在哪。。。。。。。。
36 楼 Heart.X.Raid 2009-12-18  
mali0330 写道
我就想知道你是如何看到的JVM的源代码,可以和je分享一下么?谢谢


JDK 工具包里面都有一个src文件,里面是source code,但native方法,与虚拟机相关的部分没有公开。 
35 楼 mali0330 2009-12-18  
我就想知道你是如何看到的JVM的源代码,可以和je分享一下么?谢谢
34 楼 prowl 2009-12-17  
msnvip 写道
Java代码  收藏代码
  1. public class TestFan {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      *  
  6.      * User是一个空class 
  7.      */  
  8.     public static void main(String[] args) {  
  9.         int i = 0;  
  10.         test(null);  
  11.     }  
  12.   
  13.     //  
  14.     //不添加此方法.编译,运行没问题.输出显示调用的user  
  15.     //添加此方法时候.编译期出错The method test(String) is ambiguous for the type TestFan  
  16.     public static void test(String str)  
  17.     {  
  18.         System.out.println("string");  
  19.     }  
  20.       
  21.       
  22.     public static void test(User users)  
  23.     {  
  24.         System.out.println("user");  
  25.     }  
  26.       
  27.     public static void test(Object users)  
  28.     {  
  29.         System.out.println("object");  
  30.     }  
  31. }  


请教下: 
1.没添加那个方法时候.为什么调用的会是user的那个方法? 
2.添加后为什么出错了



我觉得这可能是编译器的默认行为吧 默认
Java代码  收藏代码
  1. Object , ? extends Object   


类似多态? 

2个子类,编译器识别不了了。。。
33 楼 prowl 2009-12-17  
Heart.X.Raid 写道

class CM<MediaBean>有问题: 

首先你要搞清楚你想要定义的类型是泛型类还是普通类。 

如果是普通类,class CM implements ConvertMachine<MediaBean>就可以了。 
如果是泛型类,正如你定义的一样CM<MediaBean>,编译器会把MediaBean理解为类型变量,相当于常用的T,S,K,V之类的符号。 

既然编译器已经认定你定义的class CM<MediaBean>是一个泛型类,而且MediaBean是泛型变量的话。那么编译器理解MediaBean bean 也就认为相当于T bean。试问MediaBean(也就是T)在编译阶段不能够确定具体类型的情况下,MediaBean.getOffset()方法在哪里(也就相当于T.getOffset()方法)。 
因此,编译器报错:The method getOffset() is undefined for the type MediaBean 

注意,你定义的时候MediaBean已经是CM泛型类的类型变量了,再也不是你认为的某一个类了。一定要保持清醒。 


我大概明白了,但是如果是这样的那具体的实现类都不能用泛型来检验编译时错误,只定义接口范围的泛型没什么意义啊。 

实际上针对这种情况
Java代码  收藏代码
  1. public class A<T> extends B<T>{  
  2.     void method(T param){  
  3.         //do somthing  
  4.     }  
  5. }  


T param 等同于 

Java代码  收藏代码
  1. final T param  


局限性很大啊
32 楼 msnvip 2009-12-17  
Java代码  收藏代码
  1. public class TestFan {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      *  
  6.      * User是一个空class 
  7.      */  
  8.     public static void main(String[] args) {  
  9.         int i = 0;  
  10.         test(null);  
  11.     }  
  12.   
  13.     //  
  14.     //不添加此方法.编译,运行没问题.输出显示调用的user  
  15.     //添加此方法时候.编译期出错The method test(String) is ambiguous for the type TestFan  
  16.     public static void test(String str)  
  17.     {  
  18.         System.out.println("string");  
  19.     }  
  20.       
  21.       
  22.     public static void test(User users)  
  23.     {  
  24.         System.out.println("user");  
  25.     }  
  26.       
  27.     public static void test(Object users)  
  28.     {  
  29.         System.out.println("object");  
  30.     }  
  31. }  


请教下: 
1.没添加那个方法时候.为什么调用的会是user的那个方法? 
2.添加后为什么出错了
31 楼 Heart.X.Raid 2009-12-17  
prowl 写道
有个疑问,不知道楼主能不能给解答下。(昨天写代码时遇到的) 

接口: 

Java代码  收藏代码
  1. interface ConvertMachine<T>{  
  2.     /** 
  3.      * convert the list to byte stream. 
  4.      * @param list contains media and audio object 
  5.      * @param key  file name except expansion name 
  6.      * @return 
  7.      */  
  8.     ByteBuffer toByteStream(List<T> list,String key);  
  9.   
  10.     /** 
  11.      * be used to test 
  12.      * @param file 
  13.      */  
  14.     void reverse(File file);  
  15. }  


实现类: 

引用
    class CM<MediaBean> implements ConvertMachine<MediaBean> { 
        @Override 
        public ByteBuffer toByteStream(List<MediaBean> list, String key) { 

            ByteBuffer buf = null; 
            RandomAccessFile forh264 = null; 
            RandomAccessFile foraac = null; 

            File h264 = new File(key + ".h264"); 
            File aac = new File(key + ".aac"); 

            try { 
                int offsetsum = 0; 
                for (MediaBean bean : list) { 
                    offsetsum = offsetsum + bean.getOffset()
                } 
            //后面省略掉 
        } 

        @Override 
        public void reverse(File file) { 
            //be used to test ,implements lazy 
        } 
    } 


实现类代码,无红色部分,如写红色部分,编译器会绿色位置报错(IDEA)。 

十分不解! 





class CM<MediaBean>有问题: 

首先你要搞清楚你想要定义的类型是泛型类还是普通类。 

如果是普通类,class CM implements ConvertMachine<MediaBean>就可以了。 
如果是泛型类,正如你定义的一样CM<MediaBean>,编译器会把MediaBean理解为类型变量,相当于常用的T,S,K,V之类的符号。 

既然编译器已经认定你定义的class CM<MediaBean>是一个泛型类,而且MediaBean是泛型变量的话。那么编译器理解MediaBean bean 也就认为相当于T bean。试问MediaBean(也就是T)在编译阶段不能够确定具体类型的情况下,MediaBean.getOffset()方法在哪里(也就相当于T.getOffset()方法)。 
因此,编译器报错:The method getOffset() is undefined for the type MediaBean 

注意,你定义的时候MediaBean已经是CM泛型类的类型变量了,再也不是你认为的某一个类了。一定要保持清醒。 
30 楼 prowl 2009-12-17  
有个疑问,不知道楼主能不能给解答下。(昨天写代码时遇到的) 

接口: 

Java代码  收藏代码
  1. interface ConvertMachine<T>{  
  2.     /** 
  3.      * convert the list to byte stream. 
  4.      * @param list contains media and audio object 
  5.      * @param key  file name except expansion name 
  6.      * @return 
  7.      */  
  8.     ByteBuffer toByteStream(List<T> list,String key);  
  9.   
  10.     /** 
  11.      * be used to test 
  12.      * @param file 
  13.      */  
  14.     void reverse(File file);  
  15. }  


实现类: 

引用
    class CM<MediaBean> implements ConvertMachine<MediaBean> { 
        @Override 
        public ByteBuffer toByteStream(List<MediaBean> list, String key) { 

            ByteBuffer buf = null; 
            RandomAccessFile forh264 = null; 
            RandomAccessFile foraac = null; 

            File h264 = new File(key + ".h264"); 
            File aac = new File(key + ".aac"); 

            try { 
                int offsetsum = 0; 
                for (MediaBean bean : list) { 
                    offsetsum = offsetsum + bean.getOffset()
                } 
            //后面省略掉 
        } 

        @Override 
        public void reverse(File file) { 
            //be used to test ,implements lazy 
        } 
    } 


实现类代码,无红色部分,如写红色部分,编译器会绿色位置报错(IDEA)。 

十分不解! 



29 楼 Heart.X.Raid 2009-12-17  
http://topic.csdn.net/u/20091216/20/e882995f-8932-4a02-af87-e25229f33735.html?45935 
另外,我在也CSDN发帖了,其中3楼和6楼的回答也有道理。大家可以去看看。
28 楼 Heart.X.Raid 2009-12-17  
qianhd 写道
引用
(1) Java编译器没有泛型对象这样的一个特殊概念。它会将所有泛型类的对象全部理解成为普通类对象(这一点会在下面详细阐述)。


此处应该是笔误  在最后也提到是Java虚拟机中不存在泛型概念 
换言之 运行态没有泛型,泛型只存在于编译期 
楼主的文章用词还需要斟酌 否则容易误导别人 


另外,编译时也不是完全当作原始类型处理,比较一下泛型生成的class和原始类型生成的class大小就知道了. 

编译器定义方法签名是 方法名+参数列表 
所以我们不能写出只有返回类型不同的方法 
但是虚拟机中方法签名是 返回类型+方法名+参数列表,虚拟机知道如何定位方法. 





不是笔误,是自己考虑不周全。受教了,谢谢你,我已经修改过来了。
27 楼 Heart.X.Raid 2009-12-17  
langyu 写道
【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。 

不就表示擦除后的T变成object了,和Object类的equals(Object)方法是一样的,没有进行重写呀 
还有其它玄机吗?


是这样的。 

既然Pair<T>擦除以后equals方法变成了equals(Object),是不是和父类Object中的equals(Object)一样了,那么子类Pair<T>是不是就覆盖了Object的方法,也就是方法的重写。那么应该是override it。为什么编译器会认为没有覆盖那。 

我觉的18楼liu78778的解释可以理解,不过不知道有没有能够证实这个想法的。 

还是感谢大家的讨论。
26 楼 Heart.X.Raid 2009-12-17  
liu78778 写道
JackAndroid 写道
haojia0716 写道
编译器就是这样说的 
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)具有相同的擦除,但是未覆盖它



我觉得有道理: 

    在擦除以前编译器认为equals(T)是一个新方法,没有覆盖到Object中的equals(Object)。当编译器擦除完后发现这个equals(T)变成了equals(Object)。估计把编译器搞疯了。 
    但是不知道有没有这种想法的佐证:比如编译器是先进行类型检查再进行泛型方法的擦除工作。
25 楼 Heart.X.Raid 2009-12-17  
miaow 写道
都擦除了,没什么额外处理 


应该有额外处理,所以这几天都在看虚拟机。JVM是知道用什么具体类型来处理Object的
24 楼 Heart.X.Raid 2009-12-17  
<div class="quote_title">hankesi2000 写道</div> 
<div class="quote_div"> 
<div class="quote_title">Heart.X.Raid 写道</div> 
<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> 
<p> </p> 
<p>是的,太谢谢你了,写的太不仔细了。谢谢指出。现在已经改过来了</p>
23 楼 qianhd 2009-12-17  
Heart.X.Raid 写道
haojia0716 写道
编译器就是这样说的 
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()不也是一样的吗,为什么没有说冲突呢? 

还希望牛人指点。谢谢大家



Java代码  收藏代码
  1. //方法1     
  2. public void test(List<String> list)     
  3. {     
  4.     System.out.println("List<String>");      
  5. }     
  6.      
  7. //方法2     
  8. public ArrayList test(List<Object> list)     
  9. {     
  10.     System.out.println("List<Object>");     
  11.     return null;     
  12. }     
  13.   
  14. //方法3     
  15.   public void test(List<Object> list)     
  16.   {     
  17.       System.out.println("List<Object>");     
  18.   }     
  19.   
  20. //方法4     
  21.   public ArrayList test(List<String> list)     
  22.   {     
  23.       System.out.println("List<Object>");     
  24.       return null;     
  25.   }    



你只要想通这4个方法 自然就明白了 
22 楼 qianhd 2009-12-17  
引用
(1) Java编译器没有泛型对象这样的一个特殊概念。它会将所有泛型类的对象全部理解成为普通类对象(这一点会在下面详细阐述)。


此处应该是笔误  在最后也提到是Java虚拟机中不存在泛型概念 
换言之 运行态没有泛型,泛型只存在于编译期 
楼主的文章用词还需要斟酌 否则容易误导别人 


另外,编译时也不是完全当作原始类型处理,比较一下泛型生成的class和原始类型生成的class大小就知道了. 

编译器定义方法签名是 方法名+参数列表 
所以我们不能写出只有返回类型不同的方法 
但是虚拟机中方法签名是 返回类型+方法名+参数列表,虚拟机知道如何定位方法. 
0 0