方法重載解析

方法重載: 當兩個(或多個)方法的名稱相同,而參數的對應類型或個數不同時,我們就說方法重載了。當然,編譯器也能識別出來。

編譯器是如何識別調用了哪個方法?

  在往下講前,我們先來了解一下:編譯器是怎麼才能識別出程序調用了那個方法。其實,這個問題就是在問:在調用方法處,編譯器能得到調用方法的什麼信息,從而能找到對應的方法?我們一般的方法調用是這樣的:

method( vars );

也就是說,方法調用處,一共爲編譯器提供兩個信息:方法名、參數列表。
所以,編譯器只能通過 方法名 和 參數列表 來識別調用方法

有一道面試題問:爲什麼不能通過返回類型來重載方法?
就是上面所說的,方法調用處並沒有提供返回類型的信息,所以當多個方法只有返回類型不一樣時,編譯器就不知道調用了那個方法了。

我們已經知道了編譯器是怎麼識別方法的了,而對於方法重載,其要求方法名是一樣的,那麼我們只需要關注 參數列表 便可以了參數列表區分,或者說重載方法的區分:

  • 參數的個數

  • 參數的類型

  • 參數的順序

二、方法重載的匹配選擇

  方法重載後,方法調用處可能會遇到應該選擇哪個重載方法的問題,如果只有唯一個重載方法可以匹配,那麼就沒問題。然而,大部分情況卻是有多個重載方法是可以匹配的,那麼這時候就應該選擇最合適的重載方法.

匹配最合適、最明確的重載方法,其實就是實參列表去匹配當前重載方法中形參列表,尋找與實參列表最相近的形參列表

1、基本類型之間的重載

對於基本類型來說,從“短類型”擴展成“長類型”是默認允許、自動進行的,這就可能造成了實參可能匹配到多個“長類型”的形參,看個簡單例子:

 public static void main(String[] args) {
   short s = 4;
   m(s);
}
public static void m(int x){//方法一
   System.out.println("重載方法一");
}
public static void m(float x){//方法二
   System.out.println("重載方法二");
}

運行結果

重載方法一

short類型 可以默認自動轉換成int、'float'類型。但m(s)真正匹配選擇的是m(int x)方法,而不是形參長度更長的m(float x)。所以可以看出,基本類型的形參匹配規則是: 如果沒有匹配到精確類型的形參,則優先匹配 存儲長度(範圍)大於且是最接近實參的存儲長度的形參,從而確定調用哪個重載方法

2、引用類型間的重載

  對於引用類型來說,可以匹配到多個重載方法的原因是:引用類型的對象進行類型上轉也是JVM默認自動進行的,那麼就可能匹配多個祖先類型的形參看下面的例子:

public class Test_3 {
public static void main(String[] args)  {
  Children children = new Children();
  someMethod(children);
}  
public static void someMethod(Ancestor an) {//重載方法1
   System.out.println("this is Ancestor Method!");
}
public static void someMethod(Parent an) {//重載方法2
   System.out.println("this is Parent Method!");
}
}
//3個具有繼承關係的類
class Ancestor{//祖先類    
}
class Parent extends Ancestor{//父類,繼承於Ancestor  
}
class Children extends Parent{//子類,繼承於Parent    
}

運行結果

this is Parent Method!

  可以看出,引用類型與基本類型一樣,都是選擇”最明確的方法“, 引用類型間選擇最明確的重載方法的規則是: 如果找不到重載方法的形參的引用類型與實參一致,則實參優先匹配 在繼承樹結構上,離實參類型最近的形參,則此形參所在的重載方法便是最明確的重載方法。

3、自動裝箱拆箱、可變參數類型

  裝箱拆箱、以及可變參數列表的處理都是由編譯器自動處理,也就是說是默認自動進行的,這同樣會讓實參列表可以匹配多個形參列表 ,可以匹配多個重載方法

  此小節將會涉及到基本類型、引用類型、自動裝箱拆箱可變參數的重載方法匹配的優先級。

看下面的例子,這個例子包括很多情況:

public class Test_3 {
public static void main(String[] args)  {
  short s = 5;
  overloadMethod(s);// test1
  Integer i = 10;
  overloadMethod(i);//test2
  overloadMethod(s,s);//test3
}  
public static void overloadMethod(int a) { //m1
   System.out.println("調用  overloadMethod(int)");
}
public static void overloadMethod(Short in) {//m2
   System.out.println("調用  overloadMethod(short)");
}
public static void overloadMethod(int a,int b) {//m3
   System.out.println("調用  overloadMethod(int,int)");
}
public static void overloadMethod(short... s) { //m4
   System.out.println("調用  overloadMethod(short...)");
}
public static void overloadMethod(Integer... i) {//m5
   System.out.println("調用  overloadMethod(Integer...)");
}
}

運行結果

調用 overloadMethod(int)
調用 overloadMethod(int)
調用 overloadMethod(int,int)

我們來分析一下上面的例子中,方法調用處可以匹配到的方法:

  • test1 處的方法調用可以匹配的重載方法有:m1(基本類型的短類型自動轉爲長類型)、m2(自動裝箱)、m4(可變參數列表)

  • test2 處的方法調用可以匹配的重載方法有:m1(自動拆箱)、m5(可變參數列表);

  • test3 處的方法調用可以匹配的重載方法有:m3(基本類型的短類型自動轉換成長類型)、m4(可變參數列表)

查看輸出結果,發現:test1處選擇了m1、test2選擇了m1,test3選擇了m3。
根據這樣的結果,也就是這幾種形參匹配規則還是有個匹配的順序的。對重載方法的選擇作以下總結:

  • 先按照實參的類型(基本類型或引用類型)對應匹配規則,進行查找最相近的形參列表,從而找到最明確的重載方法;找不到,則執行第二步;

  • 對實參進行裝箱或拆箱轉換(前提是實參是基本類型或者是包裝類),再安按照轉換得到的類型進行匹配形參的類型(形參類型與轉換類型要一致,特別注意基本類型);找不到,則執行第三步;

  • 匹配形參是可變參數的重載方法,此時,形參的類型可以是 實參的類型以及通過 基本類型的短轉長、自動裝箱拆箱、祖先類型 得到的轉換類型。

將上面的總結再簡化一下,可以簡化成 重載方法的形參匹配規則的優先級:

當前類型(基本類型或引用類型)的匹配規則 > 自動裝箱拆箱 > 可變參數列表

再看一個例子:

public class MyTest {
   public static void main(String[] args) {
     int a = 5;
     short s = 8;
     m(a,s);
   }
public static void m(int a,Short b) {//m1
   System.out.println("調用了m(int,Short)");
}
public static void m(float f,short s) {//m2
   System.out.println("調用了m(float,short)");
}  
}

運行結果

調用了m(float,short)

分析: 實參都是基本類型,優先考慮形參列表都是基本類型的重載方法,找不到才考慮自動裝箱拆箱

4、泛型方法的重載

泛型方法的重載規則: 將泛型方法的類型變量擦除,然後與非泛型方法一樣,按照上面所說的三種規則一一匹配

public static void main(String[] args)  {
//創建Runnable對象
Runnable r = new Runnable() { public void run(){} };
//調用泛型方法
 m(r);
}
public static <T> void m(T t) {//m1
   System.out.println("調用了<T> void m(T)");
}
public static <T extends Runnable> void m(T t) {//m2
   System.out.println("調用了<T extends Runnable> void m(T t)");
}

運行結果

調用了  void m(T t)

上面的兩個泛型方法m(T t)進行類型擦除後是:

public static void m(Object t);
public static void m(Runnable t);

顯然,調用方法應該是m2,與運行結果相符;

5. 沒法確定的重載方法調用

  儘管編譯器會按照上面所說的三種優先級別去讓實參匹配形參,然而匹配的結果卻不一定是唯一的,也就是說會匹配到多個方法,從而無法確定調用那個方法,編譯失敗

情況一: 實參列表的所有最佳匹配的形參不在同一個方法中

public class MyTest {
   public static void main(String[] args) {
     int aa = 5;
     short ss = 8;
     m(aa,ss);//編譯不通過,無法確定調用了那個重載方法
   }
public static void m(int a,double b) {//m1
   System.out.println("調用了m(int,Short)");
}
public static void m(float f,int c) {//m2
   System.out.println("調用了m(float,short)");
}
}

分析

m(aa,ss)的調用編譯失敗,因爲實參aa的最佳匹配m(int,double)的第一個形參,而實參ss的最佳匹配則是m(float,short)的第二個形參。
因此,實參列表的(aa,ss)的最佳形參類型匹配分開在了兩個重載方法中。
注意一下,即使某個重載方法的形參列表包含最多的最相近的形參類型,只要不是全部,那麼依舊無法確定調用了哪個重載方法。

情況二 可變參數列表的特殊性 -- 無法根據可變參數的類型來重載方法

public static void m(short... s) {}
public static void m(Short... s) {}
public static void m(int... s) {}

調用測試例子:

short s = 8;
Short sl = 10;
m(s,s);//編譯不通過
m(s,sl);//編譯不通過
m(sl,sl);//編譯不通過

重寫 與 重載的區別

  • 重寫是針對父類與子類間的方法,即必須先得繼承父類的方法。而重載則沒有這種限制。

  • 重寫要求方法的 而方法重載則只需要 方法名相同,參數列表不同就行了。

  • 方法重載時,方法的調用是在編譯時期就已經確定了調用那個方法;方法重寫,則要在運行時,才能確定調用的是子類還是父類的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章