Java中的构造函数可以抛出异常吗?

233

构造函数是否允许抛出异常?

6个回答

362

是的,构造函数可以抛出异常。通常这意味着新对象会立即变为可回收的(当然可能需要一些时间才能被回收)。如果在构造函数中使自己变得可见(例如通过分配静态字段或将自己添加到集合中),那么"半构造"对象可能会保留下来。

在构造函数中抛出异常时需要小心一件事:因为调用者(通常)没有办法使用新对象,因此构造函数应该小心避免获取未管理的资源(例如文件句柄等),然后在不释放它们的情况下抛出异常。例如,如果构造函数尝试打开一个FileInputStream和一个FileOutputStream,并且第一个成功但第二个失败,则应尝试关闭第一个流。当然,如果是子类构造函数引发异常,这就变得更加困难了……这一切都变得有点棘手。这种情况并不经常发生,但值得考虑。


32
通常人们不会考虑子类抛出的异常。 - Vineet Reynolds
4
@Tarik: 那个代码示例会做到这一点 - 例如在构造函数中使用 someStaticField = this;someCollection.add(this) - Jon Skeet
3
@jonSkeet,从语法上讲,构造函数可以抛出异常并声明它们可以抛出异常。然而,构造函数应该抛出异常吗?在这里有什么最佳实践? - virusrocks
6
完全不同意。如果你向构造函数提供无效参数,我会期望它抛出异常 - 如果不这样做,那时就有问题了。 - Jon Skeet
2
@Ghilteras:相比较而言,我更倾向于用Java中异常的方式来表达错误。在我的看法中,避免使用这种惯用语是代码上的花招。同样地,当你已经使用了依赖注入而不是在代码中调用构造函数时,模拟就会更加有效。毕竟,当你调用一个构造函数时,你就限制了自己只能使用那个实现,而模拟的目的通常是模拟一个接口或抽象类,而不是完全使用实现类。 - Jon Skeet
显示剩余15条评论

79

是的,它们可以抛出异常。如果出现异常,它们只会被部分初始化,并且如果不是final,则容易受到攻击。

以下内容来自Secure Coding Guidelines 2.0

非final类的部分初始化实例可以通过finalizer攻击进行访问。攻击者在子类中重写受保护的finalize方法,并尝试创建该子类的新实例。虽然这次尝试失败了(在上面的示例中,ClassLoader的构造函数中的SecurityManager检查会引发安全异常),但攻击者仍然会忽略任何异常并等待虚拟机对部分初始化对象执行终结操作。当这种情况发生时,恶意finalize方法实现将被调用,使攻击者能够访问这个正在被终结的对象的引用。虽然该对象仅被部分初始化,但攻击者仍然可以在其上调用方法(从而规避SecurityManager检查)。


1
这是否意味着从非最终类中抛出异常是一种安全漏洞?这仍然是一个问题吗? - kroiz
1
请注意,此指南仅适用于您的代码在安全性重要的情况下使用或可能被使用的情况。例如,大多数Java代码用于没有SecurityManager的上下文中。 - Stephen C
1
这个问题可以通过在调用超类构造函数之前进行检查来规避。因为如果你在这个时候抛出异常,finalize() 方法将永远不会被调用。此外,在给实例字段分配任何值之前,应始终检查所有值,因为这样,“部分初始化”意味着“无法使用”,因此不存在安全风险。 - Holger

36

当构造函数没有收到有效的输入,或者无法以有效的方式构造对象时,它除了抛出异常并警告其调用者外别无选择。


15

是的,它可以抛出异常,你也可以在构造函数的签名中声明它,如下例所示:

public class ConstructorTest
{
    public ConstructorTest() throws InterruptedException
    {
        System.out.println("Preparing object....");
        Thread.sleep(1000);
        System.out.println("Object ready");
    }

    public static void main(String ... args)
    {
        try
        {
            ConstructorTest test = new ConstructorTest();
        }
        catch (InterruptedException e)
        {
            System.out.println("Got interrupted...");
        }
    }
}

12

是的,构造函数是允许抛出异常的。

但是,在选择应该抛出哪些异常时,要非常明智 - 是检查异常还是未经检查的异常。未经检查的异常基本上是RuntimeException的子类。

在几乎所有情况下(我无法找到一个例外),您将需要抛出已检查的异常。原因是未经检查的异常(如NullPointerException)通常是由于编程错误(例如未充分验证输入)而导致的。

已检查的异常提供的优点是,程序员被强制在实例化代码中捕获异常,并因此意识到可以创建对象实例失败。当然,只有代码审查才能发现吞咽异常的不良编程实践。


9

是的。

构造函数只是特殊的方法,和其他方法一样可以抛出异常。


你语句中重要的是“特殊方法”。因此,它们不像其他任何方法。从非 final 类的构造函数抛出异常可能会创建安全漏洞,因此在决定这样做时应特别小心。请参见上面 @Billy 的答案,其中包含 Java 安全编码准则的摘录。 - Ajoy Bhatia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接