java泛型——桥方法

【0】README

0.1)以下内容转自: http://www.cnblogs.com/ggjucheng/p/3352519.html


【1】泛型约束和局限性—— 类型擦除所带来的麻烦

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

  • 看看下面这个类SonPair
<span style="color:#000000"><code><span style="color:#000088 !important">class</span> <span style="color:#4f4f4f !important">SonPair</span> <span style="color:#000088 !important">extends</span> <span style="color:#4f4f4f !important">Pair</span><<span style="color:#4f4f4f !important">String</span>>{  
          <span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> setFirst(String fir){....}  
}</code></span>
  •  
  • 很明显,程序员的本意是想在SonPair类中覆盖父类Pair的setFirst(T fir)这个方法。但事实上,SonPair中的setFirst(String fir)方法根本没有覆盖住Pair中的这个方法。
  • 原因很简单,Pair在编译阶段已经被类型擦除为Pair了,它的setFirst方法变成了setFirst(Object fir)。 那么SonPair中setFirst(String)当然无法覆盖住父类的setFirst(Object)了。 
    这对于多态来说确实是个不小的麻烦,我们看看编译器是如何解决这个问题的。
  • 编译器 会自动在 SonPair中生成一个桥方法(bridge method ) :
<span style="color:#000000"><code><span style="color:#000088 !important">public</span> <span style="color:#000088 !important">void</span> <span style="color:#009900 !important">setFirst</span>(Object fir)
{
    setFirst((String) fir)
} </code></span>
  •  
  • 这样,SonPair的桥方法确实能够覆盖泛型父类的setFirst(Object) 了。而且桥方法内部其实调用的是子类字节setFirst(String)方法。对于多态来说就没问题了。

1.2)问题还没有完,多态中的方法覆盖是可以了,但是桥方法却带来了一个疑问:

  • 现在,假设 我们还想在 SonPair 中覆盖getFirst()方法呢?
<span style="color:#000000"><code><span style="color:#000088 !important">class</span> <span style="color:#4f4f4f !important">SonPair</span> <span style="color:#000088 !important">extends</span> <span style="color:#4f4f4f !important">Pair</span><<span style="color:#4f4f4f !important">String</span>>
{
      <span style="color:#000088 !important">public</span> String getFirst(){....}  
}  </code></span>
  •  
  • 由于需要桥方法来覆盖父类中的getFirst,编译器会自动在SonPair中生成一个 public Object getFirst()桥方法。 (干货——引入了桥方法,该方法是编译器生成的,不是程序员码出来的)
  • 但是,疑问来了,SonPair中出现了两个方法签名一样的方法(只是返回类型不同): 
    • ①String getFirst() // 自己定义的方法
    • ②Object getFirst() // 编译器生成的桥方法
  • 难道,编译器允许出现方法签名相同的多个方法存在于一个类中吗?事实上有一个知识点可能大家都不知道: 
    • ① 方法签名 确实只有方法名+参数列表 。这毫无疑问!
    • ② 我们绝对不能编写出方法签名一样的多个方法 。如果这样写程序,编译器是不会放过的。这也毫无疑问!
    • ③ 最重要的一点是: JVM会用参数类型和返回类型来确定一个方法。 一旦编译器通某种方式自己编译出方法签名一样的两个方法 (只能编译器自己来创造这种奇迹,我们程序员却不能人为的编写这种代码)。JVM还是能够分清楚这些方法的,前提是需要返回类型不一样。

1.3) 泛型类型中的方法冲突

<span style="color:#000000"><code><span style="color:#880000 !important"><em>//在上面代码中加入equals方法  </em></span>
<span style="color:#000088 !important">public</span> <span style="color:#000088 !important">class</span> Pair<T>{  
      <span style="color:#000088 !important">public</span> boolean <span style="color:#009900 !important">equals</span>(T <span style="color:#000088 !important">value</span>){  
            <span style="color:#000088 !important">return</span> (first.equals(<span style="color:#000088 !important">value</span>));  
      }  
} </code></span>
  •  
  • 这样看似乎没有问题的代码连编译器都通过不了: 
    这里写图片描述
  • 【Error】 Name clash(命名冲突): The method equals(T) of type Pair has the same erasure as equals(Object) of type Object but does not override it。

编译器说你的方法与Object中的方法冲突了。这是为什么?

  • 开始我也不太明白这个问题,觉得好像编译器帮助我们使得equals(T)这样的方法覆盖上了Object中的equals(Object)。
  • 经过大家的讨论,我觉得应该这么解释这个问题? 
    • 首先、我们都知道子类方法要覆盖,必须与父类方法具有相同的方法签名(方法名+参数列表)。而且必须保证子类的访问权限>=父类的访问权限。这是大家都知道的事实。
    • 然后、在上面的代码中,当编译器看到Pair中的equals(T)方法时,第一反应当然是equals(T)没有覆盖住父类Object中的equals(Object)了。
    • 接着、编译器将泛型代码中的T用Object替代(擦除)。突然发现擦除以后equals(T)变成了equals(Object),糟糕了,这个方法与Object类中的equals一样了。
  • 基于开始确定没有覆盖这样一个想法,编译器彻底的疯了(精神分裂)。
  • 然后得出两个结论:①坚持原来的思想:没有覆盖。但现在一样造成了方法冲突了。 ②写这程序的程序员疯了(哈哈)。
  • 再说了,拿Pair对象和T对象比较equals,就像牛头对比马嘴,哈哈,逻辑上也不通呀。

1.4)没有泛型数组一说

<span style="color:#000000"><code>Pair<<span style="color:#4f4f4f !important">String</span>>[] stringPairs=<span style="color:#000088 !important">new</span> Pair<<span style="color:#4f4f4f !important">String</span>>[<span style="color:#006666 !important">10</span>];
Pair<<span style="color:#4f4f4f !important">Integer</span>>[] intPairs=<span style="color:#000088 !important">new</span> Pair<<span style="color:#4f4f4f !important">Integer</span>>[<span style="color:#006666 !important">10</span>];</code></span>
  •  
  • 这种写法编译器会指定一个Cannot create a generic array of Pair的错误 
    • 我们说过泛型擦除之后,Pair[]会变成Pair[],进而又可以转换为Object[];
    • 假设泛型数组存在,那么
<span style="color:#000000"><code><span style="color:#4f4f4f !important">Object</span>[<span style="color:#006666 !important">0</span>]=stringPairs[<span style="color:#006666 !important">0</span>]; Ok
<span style="color:#4f4f4f !important">Object</span>[<span style="color:#006666 !important">1</span>]=intPairs[<span style="color:#006666 !important">0</span>]; Ok</code></span>
  •  
  • 这就麻烦了,理论上将Object[]可以存储所有Pair对象,但这些Pair对象是泛型对象,他们的类型变量都不一样,那么调用每一个Object[]数组元素的对象方法可能都会得到不同的记过,也许是个字符串,也许是整形,这对于JVM可是无法预料的。
  • 记住: 数组必须牢记它的元素类型,也就是所有的元素对象都必须一个样,泛型类型恰恰做不到这一点。即使Pair< String>,Pair< Integer>… 都是Pair类型的,但他们还是不一样。

总结:泛型代码与JVM

  •  虚拟机中没有泛型,只有普通类和方法。
  •  在编译阶段,所有泛型类的类型参数都会被Object或者它们的限定边界来替换。(类型擦除)
  •  在继承泛型类型的时候,桥方法的合成是为了避免类型变量擦除所带来的多态灾难。

猜你喜欢

转载自blog.csdn.net/weixin_44018338/article/details/91465437