【问题标题】:True-way solution in Java: parse 2 numbers from 2 strings and then return their sumJava中的真正解决方案:从2个字符串中解析2个数字,然后返回它们的总和
【发布时间】:2026-01-10 00:55:02
【问题描述】:

真是个愚蠢的问题。给定代码:

public static int sum(String a, String b) /* throws? WHAT? */ {
  int x = Integer.parseInt(a); // throws NumberFormatException
  int y = Integer.parseInt(b); // throws NumberFormatException
  return x + y;
}

你能判断它是否是好的 Java 吗?我在说的是,NumberFormatException 是一个未经检查的异常。您不必将其指定为sum() 签名的一部分。此外,据我了解,未经检查的异常的想法只是表明程序的实现不正确,更重要的是,捕获未经检查的异常是一个坏主意,因为它就像 在运行时修复坏程序 .

有人请澄清一下:

  1. 我应该指定NumberFormatException 作为方法签名的一部分。
  2. 我应该定义自己的已检查异常 (BadDataException),在方法内处理 NumberFormatException 并将其重新抛出为 BadDataException
  3. 我应该定义自己的检查异常 (BadDataException),以某种方式验证两个字符串,就像正则表达式一样,如果不匹配,则抛出我的 BadDataException
  4. 你的想法?

更新

想象一下,它不是一个开源框架,你应该出于某种原因使用它。您查看方法的签名并认为 - “好的,它永远不会抛出”。然后,有一天,你遇到了一个例外。正常吗?

更新 2

有一些 cmets 说我的sum(String, String) 是一个糟糕的设计。我完全同意,但是对于那些认为如果我们有好的设计就不会出现原始问题的人,这里有一个额外的问题:

问题定义如下:您有一个数据源,其中数字存储为Strings。这个来源可能是 XML 文件、网页、带有 2 个编辑框的桌面窗口等等。

您的目标是实现采用这两个Strings 的逻辑,将它们转换为ints 并显示消息框说“总和是xxx”。

无论您使用什么方法来设计/实现它,您都将拥有这 2 点内部功能

  1. String 转换为int 的地方
  2. 添加 2 个ints 的地方

我原来的帖子的主要问题是:

Integer.parseInt() 期望传递 正确 字符串。每当你传递一个错误的字符串,就意味着你的程序不正确(不是“你的用户是个白痴”)。你需要实现一段代码,一方面你有 Integer.parseInt() 和 MUST 语义,另一方面你需要在输入不正确的情况下正常 - 应该语义

简单地说:如果我只有MUST 库,我该如何实现应该语义

【问题讨论】:

  • 这绝不是一个愚蠢的问题。其实这是一个很不错的!我不完全确定,我会选择哪个选项。
  • 致您的更新:是的,根据快速失败的原则,这也不一定是坏事。
  • 我同意马丁的观点。这是 Java 编程中最不了解的方面之一,需要一些最佳实践。这可以从缺乏关于该主题的页面中看出,除了 O'Reilly 说“这是应该这样做的”之外,很少有人说。
  • 其实这是一个很好的问题。
  • 好问题!还提出了一些很好的答案:D

标签: java parsing exception


【解决方案1】:

在我看来,最好尽可能地处理异常逻辑。因此我更喜欢签名

 public static int sum(int a, int b);

有了你的方法签名,我不会改变任何东西。要么你是

  • 以编程方式使用不正确的值,您可以在其中验证您的生产者算法
  • 或从例如用户输入发送值,在这种情况下,该模块应执行验证

因此,这种情况下的异常处理成为文档问题。

【讨论】:

  • 我喜欢这个。它强制用户遵守契约,并使该方法只做一件事。
  • 我不明白这种方法的用法与用户执行 a+b 有何不同。为什么我们需要这个方法?
  • @Swati:我认为这是一个 example ,这是一种简单展示事物的好方法。 sum 方法也可以是 myVeryComplicatedMethodWhichComputesPhoneNumberOfMyIdealMatchGirl(int myID, int myLifeExpectancy)...
  • 完全同意这里。需要两个数字的sum 函数应该得到两个数字。你得到这些的方式与sum 函数无关。 正确分离你的逻辑。所以,我会将此与设计问题联系起来。
【解决方案2】:

这是个好问题。我希望更多的人会思考这样的事情。

恕我直言,如果您收到了垃圾参数,则抛出未经检查的异常是可以接受的。

一般来说,你不应该抛出BadDataException,因为你不应该使用异常来控制程序流。例外是例外。你的方法的调用者可以知道他们调用它之前是否他们的字符串是数字,所以传递垃圾是可以避免的,因此可以被认为是一个编程错误,这意味着它是可以抛出未经检查的异常。

关于声明 throws NumberFormatException - 这不是很有用,因为未选中 NumberFormatException 很少有人会注意到。但是,IDE 可以利用它并提供正确包装try/catch。一个不错的选择是也使用 javadoc,例如:

/**
 * Adds two string numbers
 * @param a
 * @param b
 * @return
 * @throws NumberFormatException if either of a or b is not an integer
 */
public static int sum(String a, String b) throws NumberFormatException {
    int x = Integer.parseInt(a); 
    int y = Integer.parseInt(b); 
    return x + y;
}

已编辑
评论者提出了有效的观点。您需要考虑如何使用它以及应用程序的整体设计。

如果该方法将在所有地方使用,并且所有调用者都处理问题很重要,则将该方法声明为抛出一个检查异常(强制调用者处理问题),但会使代码混乱try/catch 块.

另一方面,如果我们对我们信任的数据使用此方法,则如上所述声明它,因为预计它不会爆炸,并且您避免了本质上不必要的 try/catch 块的代码混乱。

【讨论】:

  • 问题是未经检查的异常经常被忽略,并可能渗透到你的调用堆栈中,对你的应用程序中不知道下面是什么的部分造成严重破坏。问题实际上是哪一段代码应该检查输入是否有效。
  • @G_H 有道理。 Java 教程这样说:“如果可以合理地期望客户端从异常中恢复,则将其设为受检异常。如果客户端无法从异常中恢复,则将其设为未受检异常”。问题是应该在哪里处理RuntimeException?应该是调用者还是应该让异常浮起来?如果是应该怎么处理?
  • 要部分回答这个问题,我见过两种方法:第一种是从较低层拦截 Exceptions 并将它们包装在对最终用户更有意义的更高级别异常中,第二种是使用可以在应用程序启动器中初始化的未捕获异常处理程序。
  • 恕我直言,在 javaDoc 中有 NumberFormatException 是 much 更重要。将它放在throws 子句中是一个很好的补充,但不是必需的。
  • To “调用你的方法的人可以在他们调用它之前知道他们的字符串是否是数字,所以传递垃圾是可以避免的”:这不是真的。验证字符串是否解析为 int 的唯一简单方法是尝试一下。尽管您可能想进行一些前期检查,但精确检查是一个 PITA。
【解决方案3】:

数字 4。如给定的那样,此方法不应将字符串作为参数,而应采用整数。在这种情况下(因为 java 包装而不是溢出)不可能发生异常。

 x = sum(Integer.parseInt(a), Integer.parseInt(b))

比什么意思更清楚 x = sum(a, b)

您希望异常发生在尽可能靠近源(输入)的位置。

至于选项 1-3,您没有定义异常,因为您希望调用者假设否则您的代码不会失败,您定义一个异常来定义在已知的失败条件下会发生什么,这对您来说是唯一的方法。 IE。如果您有一个方法是另一个对象的包装器,并且它抛出异常然后将其传递。只有当异常对您的方法来说是唯一的时,您才应该抛出自定义异常(frex,在您的示例中,如果 sum 应该只返回正结果,那么检查该异常并抛出异常是合适的,如果另一方面 java抛出一个溢出异常而不是包装,然后你会传递它,而不是在你的签名中定义它,重命名它,或者吃掉它)。

根据问题的更新进行更新:

简单地说:如果我只有 MUST 库,我该如何实现 SHOULD 语义。

解决方案是包装 MUST 库,并返回一个 SHOULD 值。在这种情况下,一个返回整数的函数。编写一个接受字符串并返回 Integer 对象的函数——要么工作,要么返回 null(如 guava 的 Ints.tryParse)。将您的验证与您的操作分开,您的操作应该采用整数。当您输入无效时,是否使用默认值调用您的操作,或者您执行其他操作,将取决于您的规范——我能说的最多的是,做出该决定的地方不太可能在操作中方法。

【讨论】:

  • 你的想法是什么?即使我有sum(int, int)parseIntFromString(String),我也会遇到同样的问题。为了获得相同的功能,无论哪种情况,我都必须编写一段代码,其中有 2 次调用 parseIntFromString() 和 1 次调用 sum()。考虑一下这种情况,如果它对您有意义 - 我认为与原始问题的观点没有区别。
  • @lok​​i2302:关键是调用者可以在它不是数字时决定什么行为是合适的。此外,如果他们不进行错误检查,那么故障发生在离赋值位置更近的一步——出于调试目的,越接近赋值,调试就越容易。如果你在做 TDD,你更有可能错过为调用者编写适当的单元测试。基本上,您不希望在方法 x 中提供一个应该是数字的字符串,然后当您尝试将其视为数字时,5 个类和 20 个方法调用会引发异常。
  • 我不明白。你是说提供int sum(String, String)的接口在现实中是不可能的吗?
  • @lok​​i2302:鉴于显示的代码,这可能是一个坏主意。如果字符串中的数字可能是一个单词,其中“2”、“two”、“dos”、“deux”、“zwo”都被视为相同,那么该签名将是合适的。但实际上,该方法给出了两个字符串并将它们视为整数。为什么你会想要那样做?这是个坏主意。
  • @lok​​i2302:您接受的答案是在传递垃圾时抛出未经检查的异常是可以的,但是强类型语言的重点是防止垃圾被传递。拥有一个期望字符串始终为整数值的方法只是自找麻烦。甚至 Integer.parseInt 也不能​​很好地处理(应该预期输入的异常是不好的,.Net 的 integer.TryParse 方法要好得多,尽管 Java 缺少 out 参数使其有点不切实际)。
【解决方案4】:

1。我应该将 NumberFormatException 指定为方法签名的一部分。

我想是的。这是一个很好的文档。

2。我应该定义自己的检查异常(BadDataException),在方法内部处理 NumberFormatException 并将其重新作为 BadDataException 抛出。

有时是的。在某些情况下,检查的异常被认为更好,但与它们一起工作却是一个 PITA。这就是为什么许多框架(例如 Hibernate)只使用运行时异常。

3。我应该定义自己的检查异常(BadDataException),以某种方式验证两个字符串,如正则表达式,如果不匹配则抛出我的 BadDataException。

从来没有。更多的工作,更少的速度(除非你期望抛出异常是一个规则),而且没有任何收获。

4。你的想法?

没有。

【讨论】:

    【解决方案5】:

    编号 4。

    我想我根本不会改变方法。 我会在调用方法或更高级别的堆栈跟踪中放置一个 try catch,在该上下文中,我可以从异常中优雅地恢复业务逻辑。

    我不确定第 3 条,因为我认为它太过分了。

    【讨论】:

      【解决方案6】:

      假设您正在编写的内容将被其他人使用(例如作为 API),那么您应该选择 1,NumberFormatException 专门用于传达此类异常,应该使用。

      【讨论】:

        【解决方案7】:
        1. 首先您需要问自己,我的方法的用户是否需要担心输入错误的数据,或者是否希望他输入正确的数据(在这种情况下为字符串)。 这种期望也被称为contract 的设计。

        2. 和 3. 是的,您可能应该定义 BadDataException 或者甚至更好地使用一些像 NumberFormatException 之类的删除项,而不是留下要显示的标准消息。在方法中捕获 NumberFormatException 并将其与您的消息一起重新抛出,不要忘记包含原始堆栈跟踪。

        3. 这取决于情况,但我可能会用一些额外的信息重新抛出 NumberFormatException。并且还必须有一个 javadoc 解释 String a, String b 的预期值是什么

        【讨论】:

          【解决方案8】:

          很大程度上取决于你所处的场景。

          案例 1. 始终由您调试代码,没有其他人,异常不会导致糟糕的用户体验

          抛出默认的 NumberFormatException

          案例 2:代码应该非常易于维护和理解

          定义您自己的异常并在抛出异常时添加更多数据以进行调试。

          您不需要正则表达式检查,因为无论如何它都会在输入错误时出现异常。

          如果是生产级代码,我的想法是定义多个自定义异常,例如

          1. 数字格式异常
          2. 溢出异常
          3. 空异常等...

          并单独处理所有这些

          【讨论】:

            【解决方案9】:
            1. 您可以这样做,以明确输入错误可能会发生这种情况。它可能会帮助使用您的代码的人记住处理这种情况。更具体地说,您明确表示您自己不在代码中处理它,或者返回一些特定的值。当然,JavaDoc 也应该明确这一点。
            2. 仅当您想强制调用方处理已检查异常时。
            3. 这似乎有点矫枉过正。依靠解析来检测错误输入。

            总体而言,NumberFormaException 未选中,因为预计会提供正确可解析的输入。输入验证是您应该处理的事情。但是,实际上解析输入是最简单的方法。您可以简单地保留您的方法,并在文档中警告需要正确的输入,并且任何调用您的函数的人都应该在使用它之前验证这两个输入。

            【讨论】:

              【解决方案10】:

              应在文档中阐明任何异常行为。要么它应该声明这个方法在失败的情况下返回一个特殊的值(比如null,通过将返回类型更改为Integer)或者应该使用案例1。如果在方法的签名中明确表示它可以让用户忽略它,如果他通过其他方式确保正确的字符串,但很明显该方法本身不能处理这种故障。

              【讨论】:

                【解决方案11】:

                回答您更新的问题。

                是的,出现“意外”异常是完全正常的。 想想刚接触编程时遇到的所有运行时错误。

                e.g ArrayIndexOutofBound
                

                对于每个循环也是一个常见的意外异常。

                ConcurrentModificationException or something like that
                

                【讨论】:

                  【解决方案12】:

                  虽然我同意应该允许渗透运行时异常的答案,但从设计和可用性的角度来看,将其包装到 IllegalArgumentException 中而不是将其作为 NumberFormatException 抛出是一个好主意。这使得你的方法的契约更加清晰,它声明一个非法的参数被传递给它,因为它抛出了一个异常。

                  关于问题的更新“想象一下,它不是一个开源框架,你应该出于某种原因使用它。你查看方法的签名并认为 - “好吧,它永远不会抛出”。然后,有一天,你有异常,正常吗?”您的方法的 javadoc 应该始终溢出您的方法的行为(前后约束)。想想说集合接口的行,如果不允许 null 的话,javadoc 说会抛出一个空指针异常,尽管它从来不是方法签名的一部分。

                  【讨论】:

                  • NumberFormatException 是 IllegalArgumentException 的子类,因此这种包装不添加任何信息。
                  • 这意味着异常可以按原样渗透(因为在这种情况下,nfe 已经是一个非法参数异常),并且可以有一个通用处理来处理调用中某处的非法参数等级制度。因此,如果这是一个用户传递参数的示例,那么可能会有一个通用代码来包装对非法参数的所有处理并通知用户。
                  【解决方案13】:

                  正如你所说的良好的 java 实践,在我看来它总是更好

                  • 要处理未经检查的异常,然后分析它并通过自定义未经检查的异常。

                  • 此外,在抛出自定义未经检查的异常时,您可以添加客户可以理解的异常消息,并打印原始异常的堆栈跟踪

                  • 无需将自定义异常声明为“抛出”,因为它是未经检查的。

                  • 这样您就不会违反使用未检查异常的用途,同时代码的客户端将很容易理解异常的原因和解决方案。

                  • 在 java-doc 中正确记录也是一种很好的做法,而且很有帮助。

                  【讨论】:

                    【解决方案14】:

                    我认为这取决于您的目的,但我至少会记录下来:

                    /**
                     * @return the sum (as an int) of the two strings
                     * @throws NumberFormatException if either string can't be converted to an Integer
                     */
                    public static int sum(String a, String b)
                      int x = Integer.parseInt(a);
                      int y = Integer.parseInt(b);
                      return x + y;
                    }
                    

                    或者,从 java.lang.Integer 类的 Java 源代码中获取一个页面:

                    public static int parseInt(java.lang.String string) throws java.lang.NumberFormatException;
                    

                    【讨论】:

                      【解决方案15】:

                      Google's 'Guava' libraryApache's 'Validator' library (comparison) 实现的输入验证模式怎么样?

                      根据我的经验,在函数的开头验证函数的参数并在适当的情况下抛出异常被认为是一种很好的做法。

                      另外,我认为这个问题在很大程度上与语言无关。此处的“良好做法”适用于所有具有可以采用有效或无效参数的函数的语言。

                      【讨论】:

                        【解决方案16】:

                        我认为您的第一句话“非常愚蠢的问题”非常相关。你为什么要写一个带有那个签名的方法呢?对两个字符串求和是否有意义?如果调用方法想要对两个字符串求和,则调用方法有责任确保它们是有效的整数,并在调用方法之前对其进行转换。

                        在这个例子中,如果调用方法不能将两个字符串转换成一个 int,它可以做几件事。这实际上取决于该求和发生在哪一层。我假设字符串转换将非常接近前端代码(如果正确完成的话),这样情况 1. 最有可能:

                        1. 设置错误消息并停止处理或重定向到错误页面
                        2. 返回 false(即,它会将总和放入其他对象中,不需要返回)
                        3. 按照您的建议抛出一些 BadDataException,但除非这两个数字的总和非常重要,否则这是矫枉过正的,就像上面提到的,这可能是糟糕的设计,因为它意味着转换是在错误的地方进行的

                        【讨论】:

                          【解决方案17】:

                          这个问题有很多有趣的答案。但我还是想补充一下:

                          对于字符串解析,我总是喜欢使用“正则表达式”。 java.util.regex 包可以帮助我们。所以我最终会得到这样的东西,它永远不会引发任何异常。如果我想捕获一些错误,我可以返回一个特殊值:

                          import java.util.regex.Pattern;
                          import java.util.regex.Matcher;
                          
                          public static int sum(String a, String b) {
                            final String REGEX = "\\d"; // a single digit
                            Pattern pattern = Pattern.compile(REGEX);
                            Matcher matcher = pattern.matcher(a);
                            if (matcher.find()) { x = Integer.matcher.group(); }
                            Matcher matcher = pattern.matcher(b);
                            if (matcher.find()) { y = Integer.matcher.group(); }
                            return x + y;
                          }
                          

                          正如我们所看到的,代码只是有点长,但我们可以处理我们想要的(并为 x 和 y 设置默认值,控制 else 子句发生的事情等......) 我们甚至可以编写一个更通用的转换例程,我们可以向其传递字符串、默认返回值、要编译的 REGEX 代码、要抛出的错误消息……

                          希望有用。

                          警告:我无法测试此代码,因此请原谅最终的语法问题。

                          【讨论】:

                          • 我们是在谈论 Java 吗? Integer.matcher 是什么? private 方法内的变量? ifs 缺少 (),缺少很多 ;xy 未声明,matcher 声明了两次,...
                          • 确实卡洛斯,我做的很匆忙,正如我所说,无法立即测试它。对不起。我想展示正则表达式的做法。
                          • 好的,但这并不能解决 NumberFormatException 的问题——IMO 的主要问题——(假设 Integer.matcher.group()Integer.parseInt(matcher.group())
                          【解决方案18】:

                          您面临这个问题是因为您让用户错误传播到应用程序的核心太深,部分还因为您滥用 Java 数据类型。

                          你应该在用户输入验证和业务逻辑之间有一个更清晰的分离,使用正确的数据类型,这个问题会自行消失。

                          事实上Integer.parseInt() 的语义是已知的——它的主要目的是解析有效 整数。您缺少明确的用户输入验证/解析步骤。

                          【讨论】:

                            最近更新 更多