是不是很好的做法,使构造函数抛出exception?

使构造函数抛出exception是一个好习惯吗? 例如我有一个classPerson ,我有age作为其唯一的属性。 现在我提供这个课程

 class Person{ int age; Person(int age) throws Exception{ if (age<0) throw new Exception("invalid age"); this.age = age; } public void setAge(int age) throws Exception{ if (age<0) throw new Exception("invalid age"); this.age = age; } } 

在构造函数中抛出exception并不是不好的习惯。 事实上,这是施工人员唯一合理的方式来表明存在问题。 例如参数无效。

但是显式声明或抛出java.lang.Exception几乎总是不好的做法。

您应该select一个与发生的exception情况相匹配的exception类。 如果抛出Exception ,则调用者很难将此exception与其他任何可能的已声明和未声明的exception分开。 这使得错误恢复困难,并且如果调用者select传播exception,问题就会扩散。


有人build议使用assert来检查参数。 这样做的问题是,通过JVM命令行设置可以打开和closuresassert断言检查。 使用断言来检查内部不variables是可以的,但使用它们来实现在javadoc中指定的参数检查不是一个好主意……因为这意味着只有在断言检查被启用时,您的方法才会严格执行规范。

assert的第二个问题是,如果一个断言失败,那么AssertionError将被抛出,并且得到的智慧是尝试捕获Error及其任何子types是一个坏主意

我一直认为在构造函数中抛出checkedexception是不好的习惯,或者至less是应该避免的东西。

原因是你不能这样做:

 private SomeObject foo = new SomeObject(); 

相反,你必须这样做:

 private SomeObject foo; public MyObject() { try { foo = new SomeObject() } Catch(PointlessCheckedException e) { throw new RuntimeException("ahhg",e); } } 

当我构buildSomeObject的时候,我知道它的参数是什么,为什么我应该期望把它包装在try catch中呢? 啊,你说,但如果我从dynamic参数构造一个对象,我不知道它们是否有效。 那么,你可以…validation参数,然后将它们传递给构造函数。 这将是一个很好的做法。 如果你关心的是参数是否有效,那么你可以使用IllegalArgumentException。

所以,而不是抛出检查exception只是做

 public SomeObject(final String param) { if (param==null) throw new NullPointerException("please stop"); if (param.length()==0) throw new IllegalArgumentException("no really, please stop"); } 

当然,有些情况下抛出一个检查的exception也许是合理的

 public SomeObject() { if (todayIsWednesday) throw new YouKnowYouCannotDoThisOnAWednesday(); } 

但多久可能呢?

正如在这里的另一个答案中提到的,在Java 安全编码指南的准则7-3中,在非最终类的构造函数中抛出一个exception会打开一个潜在的攻击向量:

准则7-3 / OBJECT-3:捍卫非最终类的部分初始化实例当非最终类中的构造函数抛出exception时,攻击者可以尝试访问该类的部分初始化实例。 确保非final类在其构造函数成功完成之前保持完全不可用状态。

从JDK 6开始,可以通过在Object构造函数完成之前抛出一个exception来防止子类的构造。 为此,请在调用this()或super()时评估的expression式中执行检查。

  // non-final java.lang.ClassLoader public abstract class ClassLoader { protected ClassLoader() { this(securityManagerCheck()); } private ClassLoader(Void ignored) { // ... continue initialization ... } private static Void securityManagerCheck() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } return null; } } 

为了与旧版本兼容,潜在的解决scheme包括使用初始化标志。 将标志设置为构造函数中的最后一个操作,然后成功返回。 为敏感操作提供通道的所有方法在继续之前必须先咨询标志:

  public abstract class ClassLoader { private volatile boolean initialized; protected ClassLoader() { // permission needed to create ClassLoader securityManagerCheck(); init(); // Last action of constructor. this.initialized = true; } protected final Class defineClass(...) { checkInitialized(); // regular logic follows ... } private void checkInitialized() { if (!initialized) { throw new SecurityException( "NonFinal not initialized" ); } } } 

此外,这些类的任何安全敏感的用途都应该检查初始化标志的状态。 在ClassLoader构造的情况下,它应该检查它的父类加载器是否被初始化。

可以通过终结器攻击来访问部分初始化的非最终类的实例。 攻击者将覆盖子类中受保护的finalize方法,并尝试创build该子类的新实例。 此尝试失败(在上例中,ClassLoader的构造函数中的SecurityManager检查会引发安全exception),但攻击者只是忽略任何exception并等待虚拟机对部分初始化的对象执行完成。 当发生这种情况时,会调用恶意的finalize方法实现,攻击者可以访问这个方法,对正在定义的对象进行引用。 虽然对象只是部分初始化,攻击者仍然可以调用它的方法,从而绕过SecurityManager检查。 尽pipe初始化的标志并不妨碍对部分初始化的对象的访问,但它确实可以防止该对象的方法对攻击者做任何有用的事情。

在安全的情况下使用初始化的标志可能是麻烦的。 只要确保公共非终结类中的所有字段包含一个安全值(例如null),直到对象初始化成功完成,就可以在对安全性不敏感的类中表示一个合理的select。

一个更健壮,但更详细的方法是使用“指向实现”(或“pimpl”)。 通过接口类转发方法调用,该类的核心被移入非公共类。 任何尝试在完全初始化之前使用该类将导致NullPointerException。 这种方法也适用于处理克隆和反序列化攻击。

  public abstract class ClassLoader { private final ClassLoaderImpl impl; protected ClassLoader() { this.impl = new ClassLoaderImpl(); } protected final Class defineClass(...) { return impl.defineClass(...); } } /* pp */ class ClassLoaderImpl { /* pp */ ClassLoaderImpl() { // permission needed to create ClassLoader securityManagerCheck(); init(); } /* pp */ Class defineClass(...) { // regular logic follows ... } } 

你不需要抛出一个检查的exception。 这是程序控制中的一个错误,所以你想抛出一个未经检查的exception。 使用Java语言已经提供的未经检查的exception之一,例如IllegalArgumentExceptionIllegalStateExceptionNullPointerException

你也可能想摆脱二传手。 您已经提供了通过构造函数启动age的方法。 一旦实例化,是否需要更新? 如果没有,请跳过二传手。 一个很好的规则,不要把事情公之于众。 从私人或默认开始,并使用final保护您的数据。 现在大家都知道这个Person已经build造好了,是不变的。 它可以放心使用。

这很可能是你真正需要的:

 class Person { private final int age; Person(int age) { if (age < 0) throw new IllegalArgumentException("age less than zero: " + age); this.age = age; } // setter removed 

抛出exception是不好的做法,因为这需要任何调用构造函数的人来捕获exception,这是一个不好的习惯。

有一个构造函数(或任何方法)抛出一个exception是一个好主意,通常是IllegalArgumentException,它是未经检查的,因此编译器不会强迫你捕获它。

你应该抛出检查exception(从exception,但不是RuntimeException扩展的东西),如果你想调用者捕捉它。

我从来没有认为在构造函数中抛出exception是一个不好的习惯。 在devise课程的时候,你应该明白这个课程的结构应该是什么。 如果其他人有不同的想法,并试图执行这个想法,那么你应该相应的错误,让用户反馈错误是什么。 在你的情况下,你可能会考虑类似的东西

 if (age < 0) throw new NegativeAgeException("The person you attempted " + "to construct must be given a positive age."); 

其中NegativeAgeException是您自己构造的exception类,可能会扩展另一个exception,如IndexOutOfBoundsException或类似的东西。

断言看起来并不是要走的路,因为你并没有试图发现代码中的错误。 我会说,终止exception是绝对正确的事情在这里做。

这是完全有效的,我一直这样做。 如果是参数检查的结果,我通常使用IllegalArguemntException。

在这种情况下,我不会build议声明,因为它们在部署构build中被closures,并且您总是希望阻止这种情况的发生,但是如果您的小组做了所有testing,并且您认为失踪的机会运行时的参数问题比抛出可能导致运行时崩溃的exception更为可接受。

另外,对于主叫方来说,断言会更困难,这很容易。

把它列为一个“抛出”在你的方法的javadocs。

我不是在构造函数中抛出exception,因为我认为这是非干净的。 我的意见有几个原因。

  1. 正如理查所说,你不能以简单的方式初始化一个实例。 特别是在testing中,只有在初始化期间通过try-catch方法来构build一个testing范围的对象,实际上是非常烦人的。

  2. 构造函数应该是无逻辑的。 根本没有理由将逻辑封装在构造函数中,因为你始终致力于分离关注和单一责任原则。 由于构造函数的关注是“构造一个对象”,如果遵循这种方法,它不应该封装任何exception处理。

  3. 闻起来很糟糕的devise。 Imho如果我被迫在构造函数中做exception处理我首先问我自己是否在我的课上有任何devise欺骗。 有时候这是必要的,但是我把它外包给build设者或者工厂来保持构造器尽可能简单。

所以如果有必要在构造函数中做一些exception处理,为什么不把这个逻辑外包给工厂的生成器呢? 这可能是几行代码,但是可以让你自由地实现一个更加健壮和适合的exception处理,因为你可以把exception处理的逻辑外包得更多,而且不会粘到构造函数上,这会封装得太多逻辑。 如果委托exception处理正确,客户端不需要知道任何有关构build逻辑的知识。

我不会从构造函数中抛出exception,主要是因为当我创build对象时,我希望它们只保存数据。 当您从构造函数中抛出exception时,您允许数据​​对象控制应用程序的逻辑stream。 使用构build器模式,您可以创buildPerson对象,同时将所有规则/条件保留在一个类中。