首页 > 如何优雅地在构造函数抛异常?
头像
小谢backup
发布于 2021-09-10 20:47
+ 关注

如何优雅地在构造函数抛异常?

1. 概述

异常将错误处理代码与应用程序的正常流程分开。在对象的实例化过程中抛出异常很常见。

在本文中,我们将研究有关在构造函数中抛出异常的所有细节。

2. 在构造函数中抛出异常

构造函数是一种特殊的方法,调用它就可以创建对象。接下来,我们将研究如何抛出异常、要抛出哪些异常以及为什么要在构造函数中抛出异常。

2.1. 怎么抛异常?

在构造函数中抛出异常跟在普通方法中抛出异常一样。我们首先创建一个带有无参数构造函数的 Animal 类:

public Animal() throws InstantiationException {
  throw new InstantiationException("Cannot be instantiated");
}

在这里,我们抛出了InstantiationException,这是一个受检查异常

2.2. 抛什么异常?

虽然我们可以抛出任何类型的异常,但我们需要一些最佳实践。

首先,我们不想抛出“ java.lang.Exception”。这是因为调用方无法识别出具体异常,从而无法处理它。

其次,如果希望调用方必须处理,我们应该抛出一个受检查异常。

第三,如果调用方无法从异常中恢复正常业务逻辑,我们应该抛出一个不受检查的异常。

需要注意的是,这些实践同样适用于方法和构造函数

2.3. 为什么要在构造函数中抛异常?

在本节中,让我们了解为什么有时候需要在构造函数中抛出异常。

参数验证。构造函数主要用于为变量赋值。如果传递给构造函数的参数非法,我们就可以抛出异常。让我们考虑一个简单的例子:

public Animal(String id, int age) {
  if (id == null)
    throw new NullPointerException("Id cannot be null");
  if (age < 0)
    throw new IllegalArgumentException("Age cannot be negative");
}

在上面的例子中,我们在初始化对象之前执行参数验证。这有助于确保我们只创建有效的对象。

在这里,如果传递给Animal对象的id为null,我们可以抛出NullPointerException。对于非 null 但仍然非法的参数,例如age为负值,我们可以抛出IllegalArgumentException。

安全检查。一些对象在创建过程中需要进行安全检查。如果构造函数执行了不安全或敏感操作,我们可以抛出异常。

让我们假设我们的 Animal类可以处理用户输入的文件:

public Animal(File file) throws SecurityException, IOException {
  if (file.isAbsolute()) {
    throw new SecurityException("Traversal attempt");
  }
  if (!file.getCanonicalPath()
    .equals(file.getAbsolutePath())) {
    throw new SecurityException("Traversal attempt");
  }
}

在上面的示例中,我们阻止了路径遍历攻击。这是通过不允许绝对路径和目录遍历来实现的。例如文件路径可能为“a/../b.txt”。在这里,canonical 路径和 absolute 路径是不同的,后者可能引发目录遍历攻击。

3. 构造函数中的继承异常

现在,让我们谈谈在子类构造函数中处理父类异常。

让我们创建一个子类Bird,它扩展了我们的Animal类:

public class Bird extends Animal {
  public Bird() throws ReflectiveOperationException {
    super();
  }
  public Bird(String id, int age) {
    super(id, age);
  }
}

由于super()必须是构造函数的第一行,我们不能简单地插入一个try-catch块来处理父类抛出的受检查异常。

由于我们的父类Animal抛出了受异常InstantiationException,因此我们无法在Bird构造函数中处理该异常。相反,我们可以向上抛出相同的异常或其父异常。

需要注意的是,这与方法重写相关的异常处理规则是不同的。在方法重写中,如果父类方法声明了异常,子类重写的方法可以声明相同、子类异常或不声明异常,但不能声明父类异常

另一方面,不受检查的异常不能声明,也不能在子类构造函数中处理。

4. 安全问题

在构造函数中抛出异常可能导致未完全初始化的对象。非 final 类的未完全初始化对象容易出现称为 Finalizer 攻击的安全问题。

简而言之,Finalizer 攻击是通过子类化部分初始化的对象并覆盖其finalize()方法,并尝试创建该子类的新实例而引起的。这可能会绕过在子类的构造函数中完成的安全检查。

覆盖finalize()方法并将其标记为final可以防止这种攻击。

好消息是,Java 9 中已弃用finalize()方法,从而防止了此类攻击。

5. 结论

在本文中,我们学习了在构造函数中抛出异常以及相关的好处和安全问题。此外,我们还研究了在构造函数中抛出异常的一些最佳实践。

全部评论

(0) 回帖
加载中...
话题 回帖