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

所以,在Java中,构造函数的第一行必须是超级调用...不管它是隐式调用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
你确定字节码仍然有效吗?我记得在有人利用下面演示的安全漏洞之后,它会失效。
额外 作者 Joshua,
有趣的一点是,这完全是Java语言的限制。等效的字节码是完全有效的。
额外 作者 Antimony,
因为规则不允许。阅读 JDK规范 。即使你通过了编译器,验证器也会拒绝它。
额外 作者 Hot Licks,

7 答案

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

所以甚至不存在不引人注目的变量或类似的东西的危险。当您尝试在基类“构造函数”之前在子类的构造函数中执行某些操作时,基本上要求编译器扩展一个不存在的基对象实例。

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

0
额外

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

Java可能允许在构造函数中围绕super()调用尝试/捕获,如果1.您覆盖超类中的所有方法,以及2.您不使用super.XXX()子句,但是这听起来太复杂了我。

0
额外

不幸的是,编译器不能理论上的原理,即使你可能知道它在你的情况下是安全的,但如果他们允许的话,它对所有情况都是安全的。

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

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

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

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

0
额外
为什么不阻止你在catch块中使用它?这涵盖了包装异常的常见情况。
额外 作者 Antimony,

这是为了防止某人从不可信代码创建新的 SecurityManager 对象。

public class Evil : SecurityManager {
  Evil()
  {
      try {
         super();
      } catch { Throwable t }
      {
      }
   }
}
0
额外

解决这个问题的一种方法是调用一个私有静态函数。 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
额外
我详细阐述了一点。足够?
额外 作者 aliteralmind,
谨慎地阐述为什么?
额外 作者 Unheilig,
为什么在这里调用一个私有静态函数?你觉得OP的代码有什么问题不起作用?
额外 作者 Unheilig,
这个问题关于继承问题,在这个代码中有组合
额外 作者 Daniel Pinyol,

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

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

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

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

0
额外

我知道这个问题有很多答案,但我想就我为什么不被允许这样做提供一点小技巧,特别是回答为什么Java不允许你这样做。所以,你去...

现在,请记住, super()必须在子类的构造函数中的任何其他位置之前调用,因此,如果您确实使用了 trycatch 块围绕着你的 super()调用,块将看起来像这样:

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

如果super() try 块中失败,它必须先在 catch 块中执行,以便 super 之前运行任何在你的子类中的构造函数。这给你带来了同样的问题:如果抛出异常,它不会被捕获。 (在这种情况下,它只会在catch块中再次抛出。)

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

现在,Java不会让你抛出一个调用 super()的异常而不是是因为异常可能被其他地方捕获,程序会继续<�不会在你的子类对象上调用 super(),并且可能是因为这个异常可能把你的对象当作参数,并尝试改变继承的实例变量的值,已经初始化。

0
额外