Java 泛型 協變式覆蓋和泛型重載


 Java 泛型 協變式覆蓋和泛型重載

 @author ixenos

 

 

 

1.協變式覆蓋(Override)

JDK 1.4及以前,子類方法如果要覆蓋超類的某個方法,必須具有完全相同的方法簽名,包括返回值也必須完全一樣。

JDK 5開始,只要子類方法與超類方法具有相同的方法簽名,或者子類方法的返回值是超類方法的子類型(增加了對協變返回值的支持),就可以覆蓋。這樣有什么好處呢?以Object類的clone方法為例:

class Object {
 ...
 public Object clone() { ... }
}

5.0以前,如果子類需要重載clone方法,必須像下面這樣寫代碼:

class Point {
 public int x;
 public int y;
 public Point(int x, int y) { this.x=x; this.y=y; }
 public Object clone() { return new Point(x,y); }
}

雖然在我們的Point類里,clone方法總是返回一個Point類型的對象,但卻必須把返回類型寫成Object,在外部使用clone方法時也必須使用惱人的強制類型轉換。

Java5.0以后,我們就可以利用新的覆蓋規則,像下面這樣編寫代碼:

class Point {
 public int x;
 public int y;
 public Point(int x, int y) { this.x=x; this.y=y; }
 public Point clone() { return new Point(x,y); }
}

 這樣,我們就可以直接使用Point p2 = p1.clone(); 而不用強制類型轉換了。


2.泛型重載(overload)

       Java方法重載一般指在同一個類中的兩個同名方法,規則是:兩個方法必須具有不同的方法簽名。因此形式參數必須不相同,使得編譯器能夠區分開這兩個重載的方法。由於編譯器不能僅僅通過方法的返回值類型來區分重載方法,所以如果兩個方法只有返回類型不同,其它完全一樣,編譯是不能通過的。(泛型、重載是java語言級別的,但擦除技術是關於實現的,它關系到合法class文件的生成,而合法的class文件才能被jvm接受,jvm本來就支持簽名相同,但返回類型不同的方法存在

 

  在java語言角度的添加這種限制也是自然的。比如兩個方法:

 

  void test(int i);

 

  int test(int i);

 

  編譯器不能確定到底應該調用哪個方法,所以這種情況在java中不允許存在。

 

  但是,對於這兩個方法test(ArrayList<String> list)和test(ArrayList<Integer> list),在java語言的級別,即編譯時,也可以是合法的重載!

  因為編譯器可以通過參數類型信息來確定調用哪個版本。再加上返回類型不同,經過編譯和類型擦除得到的兩個方法是可以在class文件中共存的。這樣問題就解決了。

 

 

泛型方法的重載時,這個規則變化如下:

class Overloaded {
 public static int sum(List<Integer> ints) {
    int sum = 0;
    for (int i : ints) sum += i;
    return sum;
 }
 public static String sum(List<String> strings) {
    StringBuffer sum = new StringBuffer();
    for (String s : strings) sum.append(s);
    return sum.toString();
 }
}

上面是兩個泛型方法的重載例子,由於Java的泛型采用擦除法實現,List<Integer>List<String>在運行時是完全一樣的,都是List類型。也就是,擦除后的方法簽名如下:

int sum(List)
String sum(List)

JVM允許這兩個方法進行重載(overload!),雖然它們的方法簽名相同(形參),只有返回值類型不同。這在兩個普通方法的重載中是不允許的。

當然了,如果兩個泛型方法的參數在擦除后相同,而且返回值類型也完全一樣,那編譯肯定是不能通過的。

類似地,一個類不能同時實現兩個具有相同擦除的接口。如Class A implements Comparable<Integer>, Comparable<Long>

    

  總結一下,

  如果兩個泛型方法在擦除泛型信息后,如果只是具有相同的參數類型,而返回值不一樣,就可以進行重載;


 

2016-09-05 17:39:18更新:

  此類泛型重載在JDK 1.7及以上編譯時已不允許。

JDK7、8是不可以編譯的,需要用JDK6才可以(答案中的均使用oracle jdk提供的編譯器)。

首先,按道理這個本來就應該報錯,從Java語言層面來說,方法重載依賴於相同的方法名、不同的參數個數、類型、順序,而List<Integer>和List<String>類型擦除后都為List<E>,從而不符合方法重載的要求。
但是,為什么會說這種依賴返回值可以通過甚至正常運行,原因在於,編譯后的倆個方法在class中的signature分別為
(Ljava/util/List<Ljava/lang/Integer;>;)I 
(Ljava/util/List<Ljava/lang/String;>;)Ljava/lang/String;
它們可以合法的共存在一個class文件中。

從jdk7開始呢,編譯期做了check,保證了behavior一致,所以報錯

參考鏈接:


作者:葫蘆娃
鏈接:https://www.zhihu.com/question/37802781/answer/75883080
來源:知乎
著作權歸作者所有,轉載請聯系作者獲得授權。

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM