为什么我不能在我的super()调用周围使用try块?

Mik*_*one 39 java exception mocking try-catch

因此,在Java中,构造函数的第一行HAS是对super的调用...是隐式调用super(),还是显式调用另一个构造函数.我想知道的是,为什么我不能试一试?

我的具体情况是我有一个测试的模拟类.没有默认的构造函数,但我想让一个更容易阅读的测试.我还想将从构造函数抛出的异常包装到RuntimeException中.

所以,我想要做的就是这样做:

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

    // Mocked methods
}
Run Code Online (Sandbox Code Playgroud)

但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
}
Run Code Online (Sandbox Code Playgroud)

这是最好的解决方法吗?为什么Java不允许我做前者?


我对"为什么"的最好猜测是Java不希望让我在一个可能不一致的状态下拥有一个构造对象......但是,在做一个模拟时,我并不关心它.看来我应该能够做到这一点......或者至少我知道上面的情况对我来说是安全的......或者好像它应该是反正的.

我正在覆盖我在测试类中使用的任何方法,因此我没有使用未初始化变量的风险.

ang*_*son 14

不幸的是,编译器无法在理论原则上工作,即使您可能知道它在您的情况下是安全的,如果他们允许,它必须对所有情况都是安全的.

换句话说,编译器并没有停止只是你,它阻止了所有人,包括那些不知道它不安全并需要特殊处理的人.这也可能是其他原因,因为如果知道如何处理它们,所有语言通常都有办法做不安全的事情.

在C#.NET中有类似的规定,声明调用基础构造函数的构造函数的唯一方法是:

public ClassName(...) : base(...)
Run Code Online (Sandbox Code Playgroud)

在这样做时,将在构造函数的主体之前调用基础构造函数,并且您无法更改此顺序.

  • 为什么不阻止你在 catch 块中使用它呢?这涵盖了包装异常的常见情况。 (2认同)

Jos*_*hua 8

这样做是为了防止有人SecurityManager从不受信任的代码创建新对象.

public class Evil : SecurityManager {
  Evil()
  {
      try {
         super();
      } catch { Throwable t }
      {
      }
   }
}
Run Code Online (Sandbox Code Playgroud)


Edw*_*rzo 6

我知道这是一个老问题,但我喜欢它,因此,我决定给它一个我自己的答案.也许我对为什么不能这样做的理解将有助于讨论和未来读者的有趣问题.

让我从一个失败的对象构造的例子开始.

让我们定义一个A类,这样:

class A {
   private String a = "A";

   public A() throws Exception {
        throw new Exception();
   }
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们假设我们想在try...catch块中创建一个类型为A的对象.

A a = null;
try{
  a = new A();
}catch(Exception e) {
  //...
}
System.out.println(a);
Run Code Online (Sandbox Code Playgroud)

显然,此代码的输出将是: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(); }
}
Run Code Online (Sandbox Code Playgroud)

C调用构造函数时,如果在初始化时发生异常B,那么最终int变量的值是b多少?

因此,对象C无法创建,它是虚假的,它是垃圾,它没有完全初始化.

对我来说,这解释了为什么你的代码是非法的.