什么时候应该抛出IllegalArgumentException?


98

我担心这是运行时异常,因此应谨慎使用。
标准用例:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

但这似乎会强制执行以下设计:

public void computeScore() throws MyPackageException {
      try {
          setPercentage(userInputPercent);
      }
      catch(IllegalArgumentException exc){
           throw new MyPackageException(exc);
      }
 }

使它回到被检查的异常。

好的,但是让我们开始吧。如果输入错误,则会出现运行时错误。首先,这实际上是一个很难统一实施的策略,因为您可能必须执行相反的转换:

public void scanEmail(String emailStr, InputStream mime) {
    try {
        EmailAddress parsedAddress = EmailUtil.parse(emailStr);
    }
    catch(ParseException exc){
        throw new IllegalArgumentException("bad email", exc);
    }
}

更糟的是-虽然0 <= pct && pct <= 100可以预期客户端代码将以静态方式进行检查,但对于更高级的数据(例如电子邮件地址)却并非如此,或更糟糕的是,必须对照数据库进行检查,因此,一般而言,客户端代码无法预先验证。

因此,基本上,我的意思是我看不到使用有意义的一致政策IllegalArgumentException。似乎不应该使用它,我们应该坚持我们自己的检查异常。有什么好的用例来抛出这个?

Answers:


80

的API文档IllegalArgumentException

抛出该错误以指示方法已传递了非法或不适当的参数。

通过查看它在JDK库中的用法,我会说:

  • 在输入可能无法正常工作之前抱怨明显的错误输入,这似乎是一种防御措施,它会导致中途失败并显示不合理的错误消息。

  • 它用于可能会很烦人的抛出检查异常的情况(尽管它出现在java.lang.reflect代码中,否则对检查异常抛出的可笑程度的担心就不明显了)。

我将使用IllegalArgumentException通用实用程序的最后沟防御参数检查(试图与JDK用法保持一致)。或者期望错误的参数是程序员错误,类似于NullPointerException。我不会用它来实现业务代码中的验证。我当然不会在电子邮件示例中使用它。


8
我认为“期望错误参数是程序员错误的建议”与我所看到的用法最一致,因此接受此答案。
djechlin

22

在谈论“错误输入”时,您应该考虑输入来自何处。

输入是由用户还是不受您控制的其他外部系统输入的,您应该期望输入无效,并始终对其进行验证。在这种情况下,抛出检查异常是完全可以的。您的应用程序应通过向用户提供错误消息来从此异常中“恢复”。

如果输入来自您自己的系统,例如您的数据库或应用程序的某些其他部分,则您应该能够依靠它来使之有效(它应该在到达那里之前已经被验证)。在这种情况下,完全可以抛出一个未经检查的异常(例如IllegalArgumentException),该异常不应被捕获(通常,您永远不应捕获未经检查的异常)。首先是无效值到达那里是程序员的错误;)您需要对其进行修复。


2
为什么“永远不应该捕获未经检查的异常”?
Koray Tugay 2015年

9
因为未检查的异常是由于编程错误而引发的。抛出此类异常的方法的调用者无法合理地期望从其中恢复,因此捕获它们通常没有任何意义。
汤姆(Tom)

1
Because an unchecked exception is meant to be thrown as a result of a programming error帮助我清除了很多东西,谢谢:)
svarog

14

“少量”抛出运行时异常并不是一个好的策略-有效的Java建议您在可以合理地期望调用者恢复的情况下使用检查的异常。(程序员错误是一个特定的示例:如果特定情况指示程序员错误,则应抛出未经检查的异常;您希望程序员对发生逻辑问题的位置进行堆栈跟踪,而不是自己尝试处理。)

如果没有恢复的希望,请随时使用未经检查的异常。抓住它们是没有意义的,所以那很好。

但是,从您的示例中并不能100%清楚地知道此示例在代码中的哪种情况。


我认为“合理预期会恢复”是令人担忧的。任何操作foo(data)都可能发生,for(Data data : list) foo(data);在此操作中,即使某些数据格式错误,调用方也可能希望尽可能多的操作成功。也包括程序错误,如果我的应用程序失败意味着不会进行交易,那可能会更好,如果这意味着核冷却脱机则很糟糕。
djechlin 2013年

StackOverflowError在某些情况下,无法合理预期呼叫者会从中恢复。但这听起来像应该检查任何数据或应用程序逻辑级别的情况。这意味着您需要对空指针进行检查!
djechlin 2013年

4
在核冷却应用程序中,我宁愿在测试中失败,也不愿允许程序员认为不可能通过而没有引起注意的情况。
Louis Wasserman

Boolean.parseBoolean(..),即使“可以合理地预期调用者可以恢复,也将引发IllegalArugmentException”。因此.....取决于您的代码来处理它或失败返回给调用者。
Jery​​l Cook

5

如oracle官方教程所述,它指出:

如果可以合理预期客户端会从异常中恢复,请将其设置为已检查的异常。如果客户端无法采取任何措施来从异常中恢复,请将其设置为未经检查的异常。

如果我有一个使用进行数据库交互的应用程序JDBC,并且我有一个将参数作为int itemand的方法double price。在price相应项目从数据库表中读取。我只需将item购买的总数乘以该price值,然后返回结果。尽管我始终确保在应用程序末尾该表中的价格字段值永远不会为负。但是,如果价格值出现负数怎么办?它表明数据库方面存在严重问题。运营商可能输入了错误的价格。这是应用程序的其他部分调用该方法无法预期并且无法从中恢复的问题。它BUG在您的数据库中。所以IllegalArguementException()在这种情况下应该抛出这样的声明the price can't be negative
我希望我已经明确表达了我的观点。


我不喜欢这个(Oracle的)建议,因为异常处理是关于如何恢复而不是是否恢复的。例如,一个格式错误的用户请求不值得使整个Web服务器崩溃。
djechlin

5

任何API都应在执行之前检查任何公共方法的每个参数的有效性:

void setPercentage(int pct, AnObject object) {
    if( pct < 0 || pct > 100) {
        throw new IllegalArgumentException("pct has an invalid value");
    }
    if (object == null) {
        throw new IllegalArgumentException("object is null");
    }
}

它们代表应用程序错误的99.9%,因为它要求进行不可能的操作,因此最终它们是应该使应用程序崩溃的错误(因此,这是不可恢复的错误)。

在这种情况下,采用快速失败的方法,应该让应用程序完成操作,以避免破坏应用程序状态。


相反,如果一个API客户端给了我一个坏的投入,我应该不会崩溃,我的整个API服务器。
djechlin

2
当然,它不应使您的API服务器崩溃,而应向调用方返回一个异常,该异常不应使客户端崩溃。
伊格纳西奥·索勒·加西亚

您在评论中写的不是您在答案中写的。
djechlin

1
让我解释一下,如果第三方客户端对参数错误(错误)的API进行了调用,则该客户端应崩溃。如果它是API服务器,而其中的一个错误则用错误的参数调用该方法,则API服务器应崩溃。检查:en.wikipedia.org/wiki/Fail-fast
Ignacio Soler Garcia

1

将其IllegalArgumentException视为前提条件检查,并考虑设计原则:一种公共方法应同时知道并公开记录其自身的前提条件。

我同意这个例子是正确的:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

如果EmailUtil是opaque,这意味着由于某些原因无法向最终用户描述前提条件,则检查的异常是正确的。第二个版本,针对该设计进行了更正:

import com.someoneelse.EmailUtil;

public void scanEmail(String emailStr, InputStream mime) throws ParseException {
    EmailAddress parsedAddress = EmailUtil.parseAddress(emailStr);
}

例如,如果EmailUtil是透明的IllegalArgumentException则它可能是所讨论的类所拥有的私有方法,并且仅当前提条件可以在功能文档中描述时才是正确的。这也是一个正确的版本:

/** @param String email An email with an address in the form abc@xyz.com
 * with no nested comments, periods or other nonsense.
 */
public String scanEmail(String email)
  if (!addressIsProperlyFormatted(email)) {
      throw new IllegalArgumentException("invalid address");
  }
  return parseEmail(emailAddr);
}
private String parseEmail(String emailS) {
  // Assumes email is valid
  boolean parsesJustFine = true;
  // Parse logic
  if (!parsesJustFine) {
    // As a private method it is an internal error if address is improperly
    // formatted. This is an internal error to the class implementation.
    throw new AssertError("Internal error");
  }
}

这种设计可以任意选择。

  • 如果前提条件的描述成本很高,或者如果该类打算由不知道其电子邮件是否有效的客户使用,请使用ParseException。这里的顶级方法被命名scanEmail,它暗示最终用户打算通过其发送未研究的电子邮件,因此这可能是正确的。
  • 如果前提条件可以在功能文档中描述,并且该类无意输入无效,并因此指示编程器错误,请使用IllegalArgumentException。尽管未“选中”,但“选中”移至记录该功能的Javadoc,希望客户端遵守该功能。IllegalArgumentException客户事先不能说出自己的论点是非法的是错误的。

关于IllegalStateException的注释:这意味着“该对象的内部状态(私有实例变量)无法执行此操作。” 最终用户看不到私有状态,因此松散地说IllegalArgumentException,在客户端调用无法得知对象状态不一致的情况下,它优先于私有状态。我没有很好的解释,它比检查异常更可取,尽管例子有两次初始化或丢失无法恢复的数据库连接。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.