当前位置: 首页 > 知识库问答 >
问题:

声明运行时异常

朱伯寅
2023-03-14

声明运行时异常的方法的指导原则是什么?

假设我html" target="_blank">调用一个抛出SQLException的第三方例程。该例程能够抛出RuntimeException而不声明它这样做是否允许/标准/可接受?

和往常一样,我对我的问题引起的困惑感到惊讶:-D这可能是因为我很困惑。

在下面的代码中,可调用的是一个lambda,它发出一个提交,这会抛出SQLException。callable.call抛出Exception。

private doThis(Callable<T> callable) throws SQLException {
    try {
        return callable.call();
    } catch (SQLException e) {
        // do stuff
        throw e;
    } catch (Exception e) {
        break; // Eats any exception from call() which makes me scream internally.
    }
}

我由此推测,程序员希望抛出一个SQLException。然而,使用Callable的性质意味着例程必须抛出异常,除非程序员做了什么。所以他抓住了这个例外,把它吞了下去。但异常是父级是RuntimeException,所以我们也在吃它们。

我该怎么办?使doThis抛出Exception似乎笨拙且随机。包装在RuntimeException中抛出的任何异常并引发保留编码器意图但似乎可疑的异常。

共有1个答案

鲁鹏
2023-03-14

不知道你的意思。

Java暗示了某些事情,通常你不能撤销这些事情。

例如,所有java文件的行为都像导入java一样。语言* 在顶部,即使不写。所有没有extends子句的类的行为就像它们有extends java一样。lang.Object。。。

无论您是否编写,所有方法的行为都像您对它们抛出RuntimeException、Error。

编写importjava.lang.*;是合法的,尽管这样做完全没有意义。编写:

void foo() throws RuntimeException {}

尽管这是毫无意义的。但是,与导入java不同。语言* ,添加一些特定的运行时异常在它记录的意义上可能很有用(它对代码的行为绝对没有任何影响)。一些线绳工具(但我不同意他们所支持的风格!)将其标记为“无用代码”,但我不会这么匆忙。例如,此方法:

public static int parseInt(String in) throws NumberFormatException {
  ..
}

声明NFEx不是为了编程目的(NFEx是一个RuntimeException,所有方法都可以在不声明它们的情况下抛出它们),但它是记录性的:它强调了在调用此方法时应该考虑的一个例外情况/这是显而易见的“您正在寻找的东西”(在这个意义上,“soooo…如果我将一个不包含数字的字符串传递给此方法,那么会发生什么”,这是一个显而易见的问题)。

所以,如果这是你的意图,那就去做吧。你的javadoc可能应该有一个:

 * @throws NumberFormatException If the provided {@code input} is not parseable as an integer number.

或类似的,对于您要声明的每个显式RuntimeException。

通常:不。声明的检查异常的概念是它们是您签名的一部分。签名完全是关于方法的作用。它根本不是关于它是如何做到的。

因此,要问的关键问题是:它的SQLException方面是您的方法所做的事情的基础,还是它如何做的基础?

一个微不足道的例子:

saveGame()可能只是“将游戏保存到某个持久存储机制”。这就是它的作用。也许今天的how涉及到一个数据库。也许明天会涉及到文件系统。也许明年它将成为一个云存储系统,涉及网络访问。如果此方法的目的仅仅是强制保存,而该方法的签名故意不解释如何进行(因为您希望将来能够灵活地更改此方法,因此任何调用方都不应假定它是磁盘、DB、网络或其他任何东西),然后,也只有在那时:SQLException将纯粹是关于如何实现的,因此它没有从saveGame方法抛出的位置。你应该把它包起来。我非常怀疑将其包装在RuntimeException中是否是一个好主意,我会发明您自己的异常(可能是SaveGameException)。是否应检查或取消检查该异常?这取决于太多的因素,无法得出这个答案。

然而,像Files.newOutputStream这样的东西是不同的。这个方法的内容包括它基于文件的概念。从根本上说:如果这个方法根本不与文件系统交互,而是打开一个数据库连接,那就太疯狂了。这个方法的名字很清楚:毕竟,它在一个名为Files的类中——它与文件交互的事实是它的一个基本的、不可删除的方面。因此,这个方法绝对应该声明为抛出IOException。同样,如果您有一个名为saveToDb的方法,则该方法绝对应该声明为抛出SQLException-因此,您不应该将SQLException包装在任何其他内容中。

在极少数情况下,便利性和预期用途强烈要求您不希望检查异常。在这种情况下,制作一个未经检查的变体并包装它。Java本身已经有Uncheck edIOException-如果是关于IOExceptions,请重用它。如果是关于SQLException,请编写相同的原则。然而,通常如果出现这种情况,您的类设计得很糟糕——如果您的系统中有一个方面从根本上与与DBs交互的概念密切相关,那么整个方法链都应该声明为抛出SQLException,此时不再需要使其未经检查。

是的,从本质上讲,RuntimeExceptions可以随时发生。看看更大的java生态系统,RuntimeExceptions涵盖了3个基本上是独立的用例(这并不是该功能的目的,这只是关于常见java库和代码实际使用它们的目的):

  • 编程错误(Bug)

典型的例子是NullPointerException。有一个错误,该错误应该会导致抛出某种RuntimeException。我们不想检查这样的异常(因为没有人喜欢一个捕获块,除非您主动编写错误,否则它实际上是不可能触发的-您可能会在catch块中添加什么?),但我们确实想抛出一些东西:断开方法并“展开”整个调用堆栈,直到某个通用处理程序处理问题(并获得有关问题发生的位置和原因的大量详细信息)正是我们想要的。因此,异常好,检查坏——这些应该是运行时异常,通常是。

类似的示例:例如,如果向许多方法传递负数,则这些方法都有关于可以传递给它们的内容的规则,而这些规则在类型系统中无法表示(例如,需要传递非负整数的方法),这些方法将抛出IllegalArgumentException。当对象处于特定状态时,无法调用某些方法,并且会引发非法状态异常(IllegalStateException)或不支持运行异常(UnsupportedOperationException),这两种方法都未选中(RuntimeException的子类型),例如,如果您尝试调用。add()在一个不可变的列表上,您会得到它。

  • 不能被打扰

有时会发生检查异常,但您根本不在乎,要么是因为您是一个懒惰的程序员,只想回家(当然,这很糟糕),要么是因为您无法理解为什么会发生检查异常。例如,在写入基于内存的输出流时发生IOException。

IDE倾向于建议这个非常愚蠢的“修复”:

try {
  code();
} catch (IOException e) {
  e.printStackTrace();
}

我不知道为什么。集体妄想?无论如何,所有IDE都是非常错误的,这是错误的代码,正确的“我现在不想考虑这个已检查的异常”句柄块是抛出新的RuntimeException(“uncaught”,e) -聪明人已经更新了他们的IDE以自动生成此代码。它只是为了方便起见,或者因为编码人员认为不会发生,将选中的异常包装到未选中的异常中。

  • 由于API/编程模型的原因

请参阅取消选中异常(UncheckedIOException),这通常是因为代码设计用于不允许选中异常的流管道中。有点罕见。

  • 意外情况

对于这个库的程序员来说,不考虑一些异国情调是合理的,但是对于您特定使用某个库时遇到的情况也是合理的。例如,第三方库可能会将数据保存到您为其提供的任何输出流中,而该输出流可能会出于某种原因引发一个记录在案的未检查异常,在这种情况下,您的库可能也会抛出它。这是很罕见的—您可能知道它,并且只有当某个库是“中介”时才会出现它—您将某个对象传递给它,然后它会对该对象进行操作,并且在该交互中,运行时异常最终会被传递。或者,您传入的对象不符合某些规则。例如,如果我将一个不符合规范的比较器(例如,不一致:a.compareTo(b)和b.compareTo(a)返回一个负数-比较器规范说不应该这样做)交给某个库,而该库最终会为此抛出一些未记录的未检查异常,我不会责怪库作者。

应记录“第一个”案例(代码错误)。例如,<代码>整数。parseInt确实记录了如果将不包含可解析内容的字符串传递给int,就会引发NumberFormatException的文档。在这种情况下,列表的添加可能不受支持的文档,并将引发不支持操作异常(UnsupportedOperationException)等。如果您的第三方例程的文档没有说明任何内容,那么它就不适用(或者文档记录不良)。

“第二种”情况(懒惰)希望也不是这样-这意味着它的程序员做得不好。

第三种情况也应记录在案。

剩下的第四个案例可能会发生,不会被记录在案。不过,你可以控制这一点。

结论:编写良好的库不会抛出您没有预料到的未记录的RuntimeException(即,处理奇怪的对象或不符合明确记录的规范的东西不是导致异常的直接原因)

 类似资料:
  • 问题内容: 我想知道以下情况是否可能。出于测试目的,我希望在应用程序上下文中为不同的测试声明不同的模拟类。这些是使用Jersey REST客户端的验收测试。有没有办法在运行时动态声明bean?Spring是否有API允许在加载上下文后更改应用程序上下文? 问题答案: 关于第一个问题,你可以在运行时通过BeanDefinitionRegistry.registerBeanDefinition()方法

  • 但是,这样做的缺点是将报告为未使用,可能是因为编译不需要它: 我不能使用依赖项,因为我不希望该依赖项传递性地继承(例如,下游依赖项可以使用log4j等)。我使用尝试了,但结果是相同的警告。 (注意,我还可以为依赖插件设置,但这似乎是一个非常生硬的工具,会隐藏其他潜在的问题。)

  • 主要内容:throws 声明异常,throw 拋出异常Java 中的异常处理除了捕获异常和处理异常之外,还包括声明异常和拋出异常。实现声明和抛出异常的关键字非常相似,它们是 throws 和 throw。 可以通过 throws 关键字在方法上声明该方法要拋出的异常,然后 在方法内部通过 throw 拋出异常对象。本节详细介绍在 Java 中如何声明异常和拋出异常。 throws 声明异常 当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部

  • 问题内容: 我有一个快速的问题。在静态关键字声明之后立即开始的代码块,那是什么类型的方法?我从未见过。如果有人能启发我,将不胜感激。谢谢。 问题答案: 这不是方法,而是类的静态Initializer块。您可以在Java Language Specification中 阅读有关它的更多信息。 加载该类后,其中的代码将执行一次。

  • 上一节展示了如何为ListOfNumbers类中的writeList方法编写异常处理程序。 有时,它适合代码捕获可能发生在其中的异常。 但在其他情况下,最好让一个方法进一步推给上层来调用堆栈处理异常。 例如,如果您将ListOfNumbers类提供为类包的一部分,则可能无法预期包的所有用户的需求。 在这种情况下,最好不要捕获异常,并允许一个方法进一步推给上层来调用堆栈来处理它。 如果writeLi