2011-03-16 57 views
3

此代碼:爲什麼這種Java方法通過聲明類型而不是運行時類型進行變形?

public class PMTest 
{ 

    private static class Runner { } 
    private static class Server extends Runner { } 

    private static class Task 
    { 
     public void delegate(Runner runner) 
     { 
      System.out.println("Task: " + runner.getClass().getName() + 
           "/" + this.getClass().getName()); 
     } 
    } 

    private static class Action extends Task 
    { 
     public void delegate(Server server) 
     { 
      System.out.println("Action: " + server.getClass().getName() + 
           "/" + this.getClass().getName()); 
     } 
    } 


    private static void foo(Task task, Runner runner) 
    { 
      task.delegate(runner); 
    } 

    private static void bar(Action task, Runner runner) 
    { 
      task.delegate(runner); 
    } 

    private static void baz(Action task, Server runner) 
    { 
      task.delegate(runner); 
    } 


    public static void main (String[] args) 
    { 
     try { 
      Server server = new Server(); 
      Action action = new Action(); 

      action.delegate(server); 
      foo(action, server); 
      bar(action, server); 
      baz(action, server); 

     } 
     catch (Throwable t) { 
      t.printStackTrace(); 
     } 
    } 
} 

產生這樣的輸出:

$ java PMTest 
Action: PMTest$Server/PMTest$Action 
Task: PMTest$Server/PMTest$Action 
Task: PMTest$Server/PMTest$Action 
Action: PMTest$Server/PMTest$Action 

我可以看得很清楚,被選擇了在行動的方法任務的方法。但我不明白爲什麼,因爲對象總是知道它們是什麼,我認爲Java的後期綁定方法選擇將能夠區分方法簽名的差異。到bar()的調用是特別令人困惑,因爲task被聲明爲Action在這一點上。

如果它的確與衆不同,這就是Java 6:

$ java -version 
java version "1.6.0_14" 
Java(TM) SE Runtime Environment (build 1.6.0_14-b08) 
BEA JRockit(R) (build R27.6.5-32_o-121899-1.6.0_14-20091001-2113-linux-ia32, compiled mode) 

我可以改變我的代碼,使其工作,但我想明白爲什麼這是行不通的。謝謝您的幫助!

回答

5

這就是調度在java中是如何工作的。

調度首先基於靜態參數的類型,並且一旦一個靜態簽名已被選定,那麼包含該方法的對象的運行時類型被用來找出使用哪個覆蓋。

例如,在

void foo(Object o) { 
    if (o instanceof Number) { foo((Number) o); } 
    else if (o instanceof String) { foo((String) o); } 
} 

void foo(String s) { ... } 

void foo(Number n) { ... } 

{ foo((Object) "foo"); } // Calls foo(Object) which calls foo(String). 
{ foo("foo"); } // Calls foo(String) without first calling foo(Object). 
+2

+1:這是正確的答案。總之,Java不會執行* double dispatch *(儘管它可以被仿真)。 –

+0

因此,調度是基於聲明的參數類型,而不是運行時參數類型?我想我可以理解實際的規則,但我不明白爲什麼會這樣實施。看起來它只是稍微遲了一點。 –

+0

好吧,看起來厄內斯特回答我的後續爲什麼 - 謝謝! –

5

選擇重載方法之間總是在編譯時間作出的,從來沒有在運行時。運行時多態性涉及在覆蓋其他方法(即具有相同簽名的方法)的方法之間進行選擇。

1

因爲一旦你通過Serverfoobar,在該範圍內調用它不是一個Server而是Runner

因此,當您運行delegate它將綁定到最合適的匹配,根據該方法的簽名。 delegate(Runner)不要求將範圍參數的危險向下轉換爲Server

注意,該作用域在運行時沒有完成,它會撐起來的源代碼靜態分析過。這只是你記得服務器是一個Server這讓你感到困惑。如果你分析的代碼沒有額外的範圍知識,那麼你會發現delegate(Runner)確實是你唯一的有效選擇。

相關問題