2008-08-07 116 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()和可能是因为该异常可能会将您的对象作为参数并尝试更改尚未初始化的继承实例变量的值。

相关问题