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

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. 结论

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

#学习路径#
全部评论

相关推荐

老粉都知道小猪猪我很久没更新了,因为秋招非常非常不顺利,emo了三个月了,接下来说一下我的情况吧本人是双非本&nbsp;专业是完全不着计算机边的非科班,比较有优势的是有两段大厂实习,美团和字节。秋招面了50+场泡池子泡死的:滴滴&nbsp;快手&nbsp;去哪儿&nbsp;小鹏汽车&nbsp;不知名的一两个小厂其中字节13场&nbsp;两次3面挂&nbsp;两次2面挂&nbsp;一次一面挂其中有2场面试题没写出来,其他的都是全a,但该挂还是挂,第三次三面才面进去字节,秋招加暑期总共面了22次字节,在字节的面评可以出成书了快手面了8场,2次实习的,通过了但没去,一次2面挂&nbsp;最后一次到录用评估&nbsp;至今无消息滴滴三面完&nbsp;没几天挂了&nbsp;所有技术面找不出2个问题是我回答不上来的,三面还来说我去过字节,应该不会考虑滴滴吧,直接给我干傻了去哪儿一天速通&nbsp;至今无消息小鹏汽车hr&nbsp;至今无消息美团2面挂&nbsp;然后不捞我了,三个志愿全部结束,估计被卡学历了虾皮二面挂&nbsp;这个是我菜,面试官太牛逼了拼多多二面挂&nbsp;3道题也全写了&nbsp;也没问题是回答不出来的&nbsp;泡一周后挂腾讯面了5次&nbsp;一次2面挂&nbsp;三次一面挂,我宣布腾讯是世界上最难进的互联网公司然后还有一些零零散散的中小厂,但是数量比较少,约面大多数都是大厂。整体的战况非常惨烈,面试机会少,就算面过了也需要和各路神仙横向对比,很多次我都是那个被比下去的人,不过这也正常,毕竟谁会放着一个985的硕士不招,反而去招一个双非读化学的小子感觉现在互联网对学历的要求越来越高了,不仅仅要985还要硕士了,双非几乎没啥生存空间了,我感觉未来几年双非想要进大厂开发的难度应该直线上升了,唯一的打法还是从大二刷实习,然后苟个转正,不然要是去秋招大概率是炮灰。而且就我面字节这么多次,已经开始问很多ai的东西了,你一破本科生要是没实习没科研懂什么ai啊,纯纯白给了
不知名牛友_:爸爸
秋招你被哪家公司挂了?
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务