2008-08-07 124 views
33

所以,在Java中,你的構造函數的第一行,必須從調用超...是它隱含調用超(),或顯式調用另一個構造。我想知道的是,爲什麼我不能在此嘗試一下?爲什麼我不能在我的super()調用中使用try塊?

我的具體情況是,我有一個測試模擬類。沒有默認的構造函數,但我想讓測試更簡單。我也想把從構造函數拋出的異常封裝到RuntimeException中。

所以,我想要做有效的是這樣的:

public class MyClassMock extends MyClass { 
    public MyClassMock() { 
     try { 
      super(0); 
     } catch (Exception e) { 
      throw new RuntimeException(e); 
     } 
    } 

    // Mocked methods 
} 

但Java抱怨說,超不是第一個語句。

我的解決方法:

public class MyClassMock extends MyClass { 
    public static MyClassMock construct() { 
     try { 
      return new MyClassMock(); 
     } catch (Exception e) { 
      throw new RuntimeException(e); 
     } 
    } 

    public MyClassMock() throws Exception { 
     super(0); 
    } 

    // Mocked methods 
} 

這是最好的解決辦法?爲什麼Java不讓我做前者?


我最好的猜測,「爲什麼」是Java不想讓我有一種潛在的不一致狀態構造的對象......然而,在做一個模擬,我不在乎關於那個。看起來我應該能夠做到以上...或者至少我知道上述對我的情況是安全的......或者似乎它應該是反正。

我重寫我從測試類中使用的任何方法,所以沒有風險,我使用未初始化的變量。

+1

有趣的是,這純粹是Java語言的限制。等效的字節碼是完全有效的。 – Antimony 2012-08-04 23:41:57

+0

你確定字節碼仍然有效嗎?我記得在有人利用下面演示的安全漏洞之後,它會失效。 – Joshua 2012-12-04 00:08:40

+0

因爲規則不允許。閱讀[JDK規範](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10)。即使你通過了編譯器,驗證器也會拒絕它。 – 2014-11-06 01:51:04

回答

14

不幸的是,編譯器不能在理論原則工作,即使你可能知道,這是你的情況的安全,如果他們允許的話,那就必須對所有的情況下是安全的。

換句話說,編譯器停不只是你,這是每個人都停止,包括所有那些不知道它是不安全的,需要特殊處理。這可能還有其他原因,因爲所有的語言通常都有辦法做到不安全如果有人知道如何處理它們。

在C#.NET也有類似的規定,並聲明調用基類的構造構造函數的唯一方法是這樣的:

public ClassName(...) : base(...) 
這樣做

,基本構造函數將主體之前被稱爲構造函數,並且你不能改變這個順序。

2

我不知道Java的是如何在內部實現,但如果超類的構造函數拋出一個異常,那麼就沒有你擴展的類的實例。例如,不可能調用toString()equals()方法,因爲它們在大多數情況下都是繼承的。

如果你重寫了超類中的所有方法,並且你不使用super.XXX()子句,那麼Java可能允許在構造方法中圍繞super()調用進行try/catch聽起來對我來說太複雜了。

2

我不能冒昧地對Java內部有深刻的理解,但我的理解是,當編譯器需要實例化一個派生類,它必須首先創建基礎(在此之前,它的底座(。 ..)),然後拍打在子類中創建的擴展。

所以它甚至沒有不良變量或類似的東西的危險。當您嘗試在子類的構造函數之前執行之前的基類「構造函數」時,基本上要求編譯器擴展尚不存在的基礎對象實例。

編輯:在你的情況,MyClass的成爲基本對象,並MyClassMock是一個子類。

5

這樣做是爲了防止有人從不可信的代碼創建新的SecurityManager對象。

public class Evil : SecurityManager { 
    Evil() 
    { 
     try { 
     super(); 
     } catch { Throwable t } 
     { 
     } 
    } 
} 
6

我知道這是一個古老的問題,但我喜歡它,因此,我決定給它一個我自己的答案。也許我理解爲什麼不能做到這一點將有助於討論和未來的讀者你有趣的問題。

讓我從失敗的對象構造的例子開始。

讓我們定義一個類A,使得:

class A { 
    private String a = "A"; 

    public A() throws Exception { 
     throw new Exception(); 
    } 
} 

現在,讓我們假設我們想在一個try...catch塊創建A類的對象。

A a = null; 
try{ 
    a = new A(); 
}catch(Exception e) { 
    //... 
} 
System.out.println(a); 

很明顯,這段代碼的輸出是:null

爲什麼Java不返回A的部分構建版本?畢竟,在構造函數失敗的時候,對象的name字段已經被初始化了,對嗎?

那麼,Java無法返回A的部分構造版本,因爲該對象沒有成功構建。該對象處於不一致狀態,因此它被Java丟棄。你的變量A甚至沒有被初始化,它保持爲空。

現在,正如你所知道的,爲了完全構建一個新的對象,它的所有超類必須首先被初始化。如果其中一個超類未能執行,那麼對象的最終狀態是什麼?這是不可能的。

看看這個更復雜的例子

class A { 
    private final int a; 
    public A() throws Exception { 
     a = 10; 
    } 
} 

class B extends A { 
    private final int b; 
    public B() throws Exception { 
     methodThatThrowsException(); 
     b = 20; 
    } 
} 

class C extends B { 
    public C() throws Exception { super(); } 
} 

C的構造函數被調用,如果在初始化B發生異常,這將是最後的int變量b的價值?

因此,對象C不能創建,它是假的,它是垃圾,它沒有完全初始化。

對我來說,這解釋了爲什麼你的代碼是非法的。

-1

解決它的一種方法是調用一個私有靜態函數。 try-catch可以放在函數體中。

public class Test { 
    public Test() { 
    this(Test.getObjectThatMightThrowException()); 
    } 
    public Test(Object o) { 
    //... 
    } 
    private static final Object getObjectThatMightThrowException() { 
    try { 
     return new ObjectThatMightThrowAnException(); 
    } catch(RuntimeException rtx) { 
     throw new RuntimeException("It threw an exception!!!", rtx); 
    } 
    } 
} 
0

我知道這個問題有很多答案,但我想給我的爲什麼這是不允許的小珍聞,特別是回答爲什麼Java不允許你這樣做。所以,在這裏你去...

現在,請記住,super()在子類的構造函數別的之前被調用,因此,如果你沒有使用你的周圍通話super()trycatch,塊必須是這樣的:

try { 
    super(); 
    ... 
} catch (Exception e) { 
    super(); //This line will throw the same error... 
    ... 
} 

如果超()fails in the嘗試block, it HAS to be executed first in theblock, so that超級runs before anything in your subclass的構造函數。這會給你帶來與開始時相同的問題:如果拋出異常,它不會被捕獲。 (在這種情況下,它只會在catch塊中再次拋出。)

現在,Java也不允許上述代碼。這段代碼可能執行第一次超級調用的一半,然後再次調用它,這可能會導致一些超級類的問題。現在

,原因是Java並不讓你拋出一個異常而不是調用super()是因爲異常可能會被別的地方捕獲,並且該計劃將繼續沒有你的子類對象上調用super()和可能是因爲該異常可能會將您的對象作爲參數並嘗試更改尚未初始化的繼承實例變量的值。

相關問題