所以,在Java中,你的构造函数的第一行是对super的调用...是隐式调用super(),还是显式调用另一个构造函数。我想知道的是,为什么我不能试试这个呢?
我的具体情况是我有一个测试的模拟类。没有默认的构造函数,但我想让一个更容易阅读的测试。我还想将构造函数抛出的异常包装成RuntimeException。
所以,我想要做的就是这样做:
public class MyClassMock extends MyClass {
public MyClassMock() {
try {
super(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Mocked methods
}
但是Java抱怨说super不是第一个声明。
我的解决方法:
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不想让我在一个可能不一致的状态下拥有一个构造对象......但是,在做一个模拟时,我并不关心它。看来我应该能够做到这一点......或者至少我知道上面的情况对我的情况是安全的......或者好像它应该是反正的。
我正在覆盖我在测试类中使用的任何方法,因此我没有使用未初始化变量的风险。
答案 0 :(得分:13)
不幸的是,编译器无法处理理论原则,即使您知道它在您的情况下是安全的,如果他们允许,它也必须对所有情况都是安全的。
换句话说,编译器并没有停止只是你,它阻止了所有人,包括那些不知道它不安全并且需要特殊处理的人。这也可能是其他原因,因为如果知道如何处理它们,所有语言通常都有办法做不安全的事情。
在C#.NET中有类似的规定,声明调用基础构造函数的构造函数的唯一方法是:
public ClassName(...) : base(...)
这样做,基础构造函数将在构造函数的主体之前调用,并且您无法更改此顺序。
答案 1 :(得分:7)
这样做是为了防止有人从不受信任的代码创建新的SecurityManager
对象。
public class Evil : SecurityManager {
Evil()
{
try {
super();
} catch { Throwable t }
{
}
}
}
答案 2 :(得分: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甚至没有初始化,它保持为null。
现在,正如您所知,要完全构建新对象,必须首先初始化其所有超类。如果其中一个超类未能执行,那么对象的最终状态是什么?无法确定这一点。
看看这个更详细的例子
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,它是虚假的,它是垃圾,它没有完全初始化。
对我来说,这解释了为什么你的代码是非法的。
答案 3 :(得分:1)
我不知道Java是如何在内部实现的,但是如果超类的构造函数抛出异常,那么就没有您扩展的类的实例。例如,调用toString()
或equals()
方法是不可能的,因为它们在大多数情况下都是继承的。
Java可以在构造函数中允许try / catch围绕super()调用,如果1.覆盖超类中的ALL方法,并且2.不使用super.XXX()子句,但是所有声音都是如此对我来说太复杂了。
答案 4 :(得分:1)
我不能假设对Java内部有深入的了解,但我的理解是,当编译器需要实例化派生类时,它必须首先创建基础(及其之前的基础(...) 。))然后拍打子类中的扩展。
所以它甚至不是非连续变量或类似的变量的危险。当您尝试在基类'构造函数之前在子类'constructor 中执行某些操作时,您基本上要求编译器扩展尚不存在的基础对象实例
编辑:在您的情况下, MyClass 成为基础对象, MyClassMock 是子类。
答案 5 :(得分:0)
我知道这个问题有很多答案,但是我想说明为什么不允许这样做,特别是为了回答为什么Java不允许你这样做。所以,你去......
现在,请记住,必须先在子类的构造函数中调用super()
之前的其他内容,因此,如果您确实使用了try
和catch
块super()
调用,块必须如下所示:
try {
super();
...
} catch (Exception e) {
super(); //This line will throw the same error...
...
}
如果super()fails in the
尝试block, it HAS to be executed first in the
捕获block, so that
超级runs before anything in your subclass
的构造函数。这会让你遇到与开头时相同的问题:如果抛出异常,它就不会被捕获。 (在这种情况下,它只会在catch块中再次抛出。)
现在,Java也不允许使用上述代码。这段代码可能会执行第一个超级调用的一半,然后再次调用它,这可能会导致一些超类的问题。
现在,Java没有让你抛出异常而不是调用super()
的原因是因为异常可以在其他地方被捕获,程序将继续< strong>没有在您的子类对象上调用super()
,并且可能因为异常可能将您的对象作为参数并尝试更改尚未初始化的继承实例变量的值。 / p>
答案 6 :(得分:-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);
}
}
}