java构造函数可以抛出异常吗_关于java:使构造函数抛出异常是一种好习惯吗?...

本问题已经有最佳答案,请猛点这里访问。

让构造函数抛出异常是一个好的实践吗?例如,我有一个类Person,我将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;

}

}

我觉得不错,但你的代码复制是不好的做法。只需从构造函数调用setage来减少大量重复的代码

在这种情况下抛出IllegalArgumentException可能是个好主意,这使得它非常明确。

@codemwnci:如果setAge是虚拟的(这里的情况也是这样),这不是一个好主意。

@你能解释一下吗?为什么虚拟方法不应该在构造函数内部调用?

@未知:这里有解释。

在构造函数中抛出异常并不是一个糟糕的实践。事实上,对于一个构造函数来说,这是表示存在问题的唯一合理的方法;例如,参数无效。

然而,明确声明或抛出java.lang.Exception几乎总是不好的做法。

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

有人建议用assert来核对论点。问题在于,可以通过jvm命令行设置打开和关闭对assert断言的检查。使用断言来检查内部不变量是可以的,但是使用它们来实现JavaDoc中指定的参数检查不是一个好主意…因为这意味着您的方法将只在启用断言检查时严格实现规范。

assert的第二个问题是,如果一个断言失败,那么AssertionError就会被抛出,得到的智慧是试图抓住Error及其任何子类型是一个坏主意。

我有一个例子,在这个例子中,从代码的异常中恢复是不可能的。您需要手动修复才能恢复。例如,对象构造函数从配置文件加载测试数据(用户名、密码等)。然后,所有测试都使用config对象中的数据。如果找不到文件、数据格式错误等,则会引发异常。我认为我们从异常中恢复的唯一方法是人为修复与文件相关的问题。那么,在这种情况下抛出异常可以吗?

IMO,不。抛出一个特定的异常是很好的做法,而不是java.lang.Exception。如果没有别的,它将帮助人类读取stacktrace,诊断问题并修复它。如果无法识别要引发的适当现有异常,则声明自定义异常大约需要10行代码。

我一直认为在构造函数中抛出检查过的异常是一种糟糕的实践,或者至少是应该避免的事情。

原因是您不能这样做:

private SomeObject foo = new SomeObject();

相反,您必须这样做:

private SomeObject foo;

public MyObject() {

try {

foo = new SomeObject()

} Catch(PointlessCheckedException e) {

throw new RuntimeException("ahhg",e);

}

}

当我构建某个对象时,我知道它的参数是什么。那么,为什么要期望我把它包装在一个试捕获中呢?你说,但是如果我用动态参数构造一个对象,我不知道它们是否有效。好吧,你可以…在将参数传递给构造函数之前先验证这些参数。那是个很好的练习。如果您只关心参数是否有效,那么可以使用IllegalArgumentException。

所以不要抛出选中的异常,只要

public SomeObject(final String param) {

if (param==null) throw new NullPointerException("please stop");

if (param.length()==0) throw new IllegalArgumentException("no really, please stop");

}

当然,在某些情况下,抛出检查过的异常可能是合理的。

public SomeObject() {

if (todayIsWednesday) throw new YouKnowYouCannotDoThisOnAWednesday();

}

但这种可能性多久一次?

你并不总是一个阶级的生产者和消费者。也就是说,其他人可能在不检查的情况下使用您的类。你可以很容易地说,他们没有满足你使用类和gigo的先决条件,但是OP询问了练习的好坏。我认为,让你的班级更容易使用和更可靠是一种良好的实践,自由地接受你作为输入有助于实现这一点。

我认为这归根结底是关于已检查和未检查异常的更大争论。选中的异常通常被错误地用作一种响应,而不是异常情况的指示。很少有事情比构造函数中的错误更特殊。我认为在绝对必要的情况下抛出运行时异常比试图向用户指出发生了forseen事件要好。如果可以的话,那也不例外吧?通常,我发现您可以使用IllegalstateException、NullPointerException或IllegalArgumentException。

我觉得手术室一般都在问例外情况。我同意你的观点,大多数案件都是由你提到的3个具体例外处理的。

我认为他的例子显示了检查异常,当然我的答案是关于检查异常的。

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

Guideline 7-3 / OBJECT-3: Defend against partially initialized

instances of non-final classes When a constructor in a non-final class

throws an exception, attackers can attempt to gain access to partially

initialized instances of that class. Ensure that a non-final class

remains totally unusable until its constructor completes successfully.

From JDK 6 on, construction of a subclassable class can be prevented

by throwing an exception before the Object constructor completes. To

do this, perform the checks in an expression that is evaluated in a

call to this() or super().

// 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;

}

}

For compatibility with older releases, a potential solution involves

the use of an initialized flag. Set the flag as the last operation in

a constructor before returning successfully. All methods providing a

gateway to sensitive operations must first consult the flag before

proceeding:

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"

);

}

}

}

Furthermore, any security-sensitive uses of such classes should check

the state of the initialization flag. In the case of ClassLoader

construction, it should check that its parent class loader is

initialized.

Partially initialized instances of a non-final class can be accessed

via a finalizer attack. The attacker overrides the protected finalize

method in a subclass and attempts to create a new instance of that

subclass. This attempt fails (in the above example, the

SecurityManager check in ClassLoader's constructor throws a security

exception), but the attacker simply ignores any exception and waits

for the virtual machine to perform finalization on the partially

initialized object. When that occurs the malicious finalize method

implementation is invoked, giving the attacker access to this, a

reference to the object being finalized. Although the object is only

partially initialized, the attacker can still invoke methods on it,

thereby circumventing the SecurityManager check. While the initialized

flag does not prevent access to the partially initialized object, it

does prevent methods on that object from doing anything useful for the

attacker.

Use of an initialized flag, while secure, can be cumbersome. Simply

ensuring that all fields in a public non-final class contain a safe

value (such as null) until object initialization completes

successfully can represent a reasonable alternative in classes that

are not security-sensitive.

A more robust, but also more verbose, approach is to use a"pointer to

implementation" (or"pimpl"). The core of the class is moved into a

non-public class with the interface class forwarding method calls. Any

attempts to use the class before it is fully initialized will result

in a NullPointerException. This approach is also good for dealing with

clone and deserialization attacks.

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

...

}

}

我想知道是否有人意识到这个微妙的问题的重要性。当决定从构造函数抛出异常时,需要注意这一点。

当我评论我的答案时,大多数Java代码不需要处理这种"攻击"。只有在安全敏感的上下文中运行不受信任的代码时,它才相关。

这只是在非常有限的情况下的一个问题,在这种情况下,部分初始化的类可能是一个安全问题。我不认为有人会建议把这一建议应用于所有的课程。

"迈克:是的,Java安全编码指南确实说这是针对敏感的用例,但即使大多数人都不必担心,它仍然是一个考虑因素。就我个人而言,我更喜欢一般的后期构造验证,与Spring很好地集成,并且可以重用为中间状态。

构造后验证允许存在尚未验证的对象,因此可以以无效状态存在。在大多数情况下,我认为这是一个更大的问题。我不知道为什么从构造函数抛出异常不能很好地与Spring集成。

@迈克,请不要对我的评论读得比我所说的更多。我从来没有说过,从一条Python身上抛出一个例外不会很好地与弹簧结合。Spring提供了构造后验证的功能,遵循这种模式可以减少从构造函数抛出异常的需要。

@Hazok-你有什么攻击如何利用这个的例子吗?

您不需要抛出选中的异常。这是程序控制范围内的一个bug,因此您希望抛出一个未经检查的异常。使用Java语言已经提供的未检查异常之一,如EDCOX1×9,EDCOX1,10,或EDCOX1,11。

你也可能想摆脱二传手。您已经提供了通过构造函数启动age的方法。实例化后是否需要更新?如果没有,跳过setter。一个好的规则,不要把事情公开化。从private或default开始,用final保护您的数据。现在大家都知道,Person的构造是正确的,是不变的。它可以自信地使用。

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

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

我认为这是对OP问题最准确的回答。尽管其他答案是一般性的,可以在其他问题中找到。

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

在这种情况下,我不会建议断言,因为它们在部署构建中被关闭,并且您总是希望阻止这种情况的发生,但是如果您的组在断言打开的情况下进行所有测试,并且您认为在运行时丢失参数问题的机会比抛出可能更容易接受的异常可能导致运行时崩溃。

另外,断言对于调用者来说更难陷阱,这很容易。

您可能希望在方法的javadocs中将其列为一个"throw",并列出原因,这样调用方就不会感到惊讶了。

我从未认为在构造函数中抛出异常是一种糟糕的实践。当类被设计时,您有一个关于该类的结构应该是什么的概念。如果其他人有不同的想法并试图执行该想法,那么您应该相应地出错,并向用户反馈错误是什么。在你的情况下,你可以考虑

if (age < 0) throw new NegativeAgeException("The person you attempted" +

"to construct must be given a positive age.");

其中NegativeAgeException是您自己构建的一个异常类,可能扩展了另一个异常,如IndexOutOfBoundsException或类似的类。

断言似乎也不完全是可行的,因为您不想在代码中发现错误。我想说,在这里终止一个例外是绝对正确的。

抛出异常是一种糟糕的实践,因为这要求调用构造函数的任何人捕获异常,这是一种糟糕的实践。

最好让一个构造函数(或任何方法)抛出一个异常,一般来说,这是未选中的IllegalArgumentException,因此编译器不会强制您捕获它。

如果希望调用者捕获检查的异常(从异常扩展而不是运行时异常),则应该抛出它。

编译器"强迫你捕捉事物"的观点是误导性的。通常,您只需声明您的方法来抛出相同的异常——直接返回到main方法——这意味着您不必强制捕获任何东西。唯一一种情况是,编译器强制你去捕捉事物,当你重写一个方法时,这个方法不会在throws子句中声明异常或它的超类。

抱歉,应该写"强制处理",意思是捕获或添加throw子句。

我不赞成在构造函数中抛出异常,因为我认为这是不干净的。我的观点有几个原因。

正如Richard提到的,您不能以简单的方式初始化实例。尤其是在测试中,只通过在初始化期间将一个测试范围的对象包围在一个try-catch中来构建它是非常烦人的。

构造函数应该是无逻辑的。完全没有理由将逻辑封装在构造函数中,因为您总是以关注点分离和单一责任原则为目标。由于构造函数的关注点是"构造一个对象",因此如果遵循这种方法,它不应该封装任何异常处理。

闻起来像是坏设计。imho如果我被迫在构造函数中进行异常处理,我首先会问自己在我的类中是否有设计欺诈。有时是必要的,但是我把它外包给一个建设者或工厂,以尽可能地保持建设者的简单。

因此,如果有必要在构造函数中进行一些异常处理,为什么不将此逻辑外包给工厂的构建者呢?这可能需要更多的代码行,但可以让您自由地实现更健壮、更适合的异常处理,因为您可以将逻辑更多地外包给异常处理,并且不会粘到构造函数上,因为构造函数会封装太多的逻辑。如果您正确地委托异常处理,那么客户机就不需要知道任何关于构建逻辑的信息。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值