48

因此,在 Java 中,构造函数的第一行必须是对 super 的调用……无论是隐式调用 super(),还是显式调用另一个构造函数。我想知道的是,为什么我不能在它周围放一个 try 块?

我的具体情况是我有一个模拟类进行测试。没有默认构造函数,但我想要一个使测试更易于阅读。我还想将从构造函数抛出的异常包装到 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 不希望我的构造对象处于可能不一致的状态……但是,在进行模拟时,我不在乎。看来我应该能够做到以上...或者至少我知道以上对于我的情况是安全的...或者似乎无论如何都应该这样做。

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

4

7 回答 7

18

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

换句话说,编译器不仅会阻止你,它还会阻止所有人,包括所有不知道它不安全且需要特殊处理的人。这可能还有其他原因,因为如果一个人知道如何处理它们,所有语言通常都有办法做不安全的事情。

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

public ClassName(...) : base(...)

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

于 2008-08-07T21:32:34.367 回答
10

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

public class Evil : SecurityManager {
  Evil()
  {
      try {
         super();
      } catch { Throwable t }
      {
      }
   }
}
于 2008-09-16T20:55:01.563 回答
9

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

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

让我们定义一个类 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,它是伪造的,是垃圾,它没有完全初始化。

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

于 2012-04-21T17:09:23.910 回答
1

我不知道Java是如何在内部实现的,但是如果超类的构造函数抛出异常,那么就没有你扩展的类的实例。例如,不可能调用toString()orequals()方法,因为它们在大多数情况下都是继承的。

Java 可能允许在构造函数中对 super() 调用进行 try/catch,如果 1. 您覆盖超类中的所有方法,并且 2. 您不使用 super.XXX() 子句,但这一切听起来都太复杂了我。

于 2008-08-07T21:41:59.917 回答
1

我不能假设对 Java 内部有深入的了解,但我的理解是,当编译器需要实例化派生类时,它必须首先创建基类(及其之前的基类(...))然后拍打子类中的扩展。

因此,甚至没有未初始化的变量或类似的东西的危险。当您尝试在基类的构造函数之前在子类的构造函数中执行某些操作时,您基本上是在要求编译器扩展一个尚不存在的基对象实例。

编辑:在您的情况下,MyClass成为基础对象,而MyClassMock是子类。

于 2008-08-07T21:43:37.463 回答
0

我知道这个问题有很多答案,但我想谈谈为什么不允许这样做,特别是回答为什么 Java 不允许你这样做。所以给你...

现在,请记住,super()必须在子类的构造函数中的任何其他内容之前调用它,因此,如果您确实在调用周围使用try和块,则块必须如下所示:catchsuper()

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

如果super()在块中失败try,它必须在块中首先执行catch,以便super在子类构造函数中的任何内容之前运行。这给您留下了与开始时相同的问题:如果抛出异常,则不会被捕获。(在这种情况下,它只是在 catch 块中再次被抛出。)

现在,Java 也不允许使用上述代码。这段代码可能会执行第一次超级调用的一半,然后再次调用它,这可能会导致某些超级类出现一些问题。

现在,Java 不允许您抛出异常而不是调用super()的原因是因为异常可能在其他地方被捕获,并且程序将继续而不调用super()您的子类对象,并且可能是因为异常可能会将您的对象作为参数并尝试更改尚未初始化的继承实例变量的值。

于 2017-04-17T15:53:27.537 回答
-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);
     }
  }
}
于 2014-03-28T19:35:33.583 回答