【问题标题】:Java constructor style: check parameters aren't nullJava 构造函数风格:检查参数不为空
【发布时间】:2011-03-01 04:17:05
【问题描述】:

如果你有一个类接受一些参数但它们都不允许是null,那么最佳实践是什么?

以下是显而易见的,但例外有点不明确:

public class SomeClass
{
     public SomeClass(Object one, Object two)
     {
        if (one == null || two == null)
        {
            throw new IllegalArgumentException("Parameters can't be null");
        }
        //...
     }
}

这里的异常让你知道哪个参数是空的,但是构造函数现在很丑:

public class SomeClass
{
     public SomeClass(Object one, Object two)
     {
        if (one == null)
        {
            throw new IllegalArgumentException("one can't be null");
        }           
        if (two == null)
        {
            throw new IllegalArgumentException("two can't be null");
        }
        //...
  }

这里的构造函数更简洁了,但是现在构造函数代码实际上不在构造函数中:

public class SomeClass
{
     public SomeClass(Object one, Object two)
     {
        setOne(one);
        setTwo(two);
     }


     public void setOne(Object one)
     {
        if (one == null)
        {
            throw new IllegalArgumentException("one can't be null");
        }           
        //...
     }

     public void setTwo(Object two)
     {
        if (two == null)
        {
            throw new IllegalArgumentException("two can't be null");
        }
        //...
     }
  }

这些样式中哪种最好?

或者有没有更广泛接受的替代方案?

【问题讨论】:

  • 我推荐2号。仅仅因为它看起来很丑并不意味着它不合适。请记住,代码是供人类阅读和理解的,而不是机器。
  • 第二种和第三种方法之间的行为差​​异对于合理回答这个问题非常重要。第二个允许设置者之后将值设置为null。如果您想要一致的行为,那么您无论如何都应该选择 3,这不再是样式问题。
  • @BalusC 假设 2 和 3 都有二传手。如果 2 没有任何 setter 方法,那么它与 3 基本相同;除非用户可以在创建对象后设置对象。

标签: java constructor null coding-style


【解决方案1】:

老问题;另一个新答案(另一条评论已经提到过;但我认为值得自己回答)。

Java 7 将java.util.Objects.requireNonNull() 添加到每个人都可以使用的 API 中。因此,检查 null 的所有参数归结为一个简短的列表,例如:

this.arg1 = Objects.requireNonNull(arg1, "arg1 must not be null");
this.arg2 = Objects.requireNonNull(arg2, "arg2 must not be null");

旁注:

  • 确保不要颠倒这两个参数 - 第二个 是用于 NPE 的消息,如果 第一个参数 为空(如果你把它们倒过来,那么你的支票就永远不会失败)
  • 另一个最佳实践:如果可能,将所有类成员设为 final(这样您就可以确定:当成功创建某个对象时,其所有成员都不为空;并且它们不会随着时间而改变)

【讨论】:

  • 这应该是现在公认的答案。不需要第三方库 + 更新的 JDK....
  • 我不介意 :-) 但是,你应该告诉提问者 :-) x2
【解决方案2】:

您可以使用众多旨在促进前提条件检查的库之一。 Google Guava 中的许多代码使用 com.google.common.base.Preconditions

在您自己的方法开始时调用简单的静态方法来验证参数和状态是否正确。这允许诸如

之类的构造
 if (count <= 0) {
   throw new IllegalArgumentException("must be positive: " + count);
 }

换成更紧凑的

 checkArgument(count > 0, "must be positive: %s", count);

它有checkNotNull,即used extensively within Guava。然后你可以写:

 import static com.google.common.base.Preconditions.checkNotNull;
 //...

 public SomeClass(Object one, Object two) {
     this.one = checkNotNull(one);
     this.two = checkNotNull(two, "two can't be null!");
     //...
 }

大多数方法都被重载为不接收错误消息、固定错误消息或带有可变参数的模板化错误消息。


开启IllegalArgumentExceptionNullPointerException

虽然您的原始代码在 null 参数上抛出 IllegalArgumentException,但 Guava 的 Preconditions.checkNotNull 会抛出 NullPointerException

这里引用Effective Java 2nd Edition: Item 60: Favor the use of standard exceptions

可以说,所有错误的方法调用都归结为非法参数或非法状态,但其他例外情况通常用于某些类型的非法参数和状态。如果调用者在某个参数中传递了null,而该参数被禁止为空值,则约定要求抛出NullPointerException,而不是IllegalArgumentException

NullPointerException 并非仅在您访问 null 引用的成员时保留;当参数是 null 时抛出它们是非常标准的,而这是一个非法值。

System.out.println("some string".split(null));
// throws NullPointerException

【讨论】:

【解决方案3】:

Java 中检查先决条件的方法比较 - Guava vs. Apache Commons vs. Spring Framework vs. Plain Java Asserts

public static void fooSpringFrameworkAssert(String name, int start, int end) {
        // Preconditions
        Assert.notNull(name, "Name must not be null");
        Assert.isTrue(start < end, "Start (" + start + ") must be smaller than end (" + end + ")");

        // Do something here ...
    }

    public static void fooApacheCommonsValidate(String name, int start, int end) {
        // Preconditions
        Validate.notNull(name, "Name must not be null");
        Validate.isTrue(start < end, "Start (%s) must be smaller than end (%s)", start, end);

        // Do something here ...
    }

    public static void fooGuavaPreconditions(String name, int start, int end) {
        // Preconditions
        Preconditions.checkNotNull(name, "Name must not be null");
        Preconditions.checkArgument(start < end, "Start (%s) must be smaller than end (%s)", start, end);

        // Do something here ...
    }

    public static void fooPlainJavaAsserts(String name, int start, int end) {
        // Preconditions
        assert null != name : "Name must not be null";
        assert start < end : "Start (" + start + ") must be smaller than end (" + end + ")";

        // Do something here ...
    }

这是本文的摘要: http://www.sw-engineering-candies.com/blog-1/comparison-of-ways-to-check-preconditions-in-java

【讨论】:

【解决方案4】:

我假设您谈论的是 Java 中内置的 assert。在我看来,使用它并不是一个好主意。因为它可以使用命令行参数打开/关闭。 Therefore some says it is only acceptable to use in private methods.

我的导师告诉我不要重新发明轮子!他们的建议是使用库。它们(可能)经过精心设计和测试。当然,您有责任确保获得优质图书馆。

其他人告诉我,Enterprise ppl(在某些方面)是错误的,并且您引入了比所需更多的依赖项(对于简单任务)。我也可以接受这一点......但这是我的最新经验:

首先我编写了自己的私有方法来检查空参数。这是无聊和多余的。我知道我应该把它放到一个实用程序类中。但是,既然有人已经写过了,我为什么还要一开始就写呢?我可以节省时间而不是编写单元测试并设计现有的东西。除非您想锻炼或学习,否则我不建议您这样做。

我最近开始使用 google 的 guava,我发现 - 连同 apache commons - 一旦你开始使用它们,你就不会只使用那一种方法。你会越来越多地发现和使用它。最后,这将使您的代码更短、更易读、更一致且更易于维护。

顺便说一句:根据您的目标,我会使用上面提到的库之一选择 2 或 3...

【讨论】:

    【解决方案5】:

    除了上面给出的所有答案都是有效和合理的之外,我认为最好指出检查 null 可能不是必要的“良好做法”。 (假设 OP 以外的读者可能会认为这个问题是教条的)

    来自 Misko Hevery 关于可测试性的博客: To Assert or Not To Assert

    【讨论】:

    • 与检查的构造函数的可测试性有关的有趣点。请在您的答案中添加链接内容的摘要(以防链接失效)。
    【解决方案6】:

    除了运行时检查之外或代替运行时检查,静态分析的注释也很有用。

    例如,FindBugs 提供了一个 @NonNull 注释。

    public SomeClass(@NonNull 对象一,@NonNull 对象二) {

    【讨论】:

    【解决方案7】:

    第二个或第三个。

    因为它会告诉 API 的用户到底出了什么问题。

    为了减少冗长,请使用 commons-lang 中的Validate.notNull(obj, message)。因此,您的构造函数将如下所示:

    public SomeClass(Object one, Object two) {
        Validate.notNull(one, "one can't be null");
        Validate.notNull(two, "two can't be null");
        ...
    }
    

    将检查放在 setter 中也是可以接受的,具有相同的详细注释。如果您的 setter 还具有保持对象一致性的作用,您也可以选择第三个。

    【讨论】:

    • 为什么把它放在二传手有争议?我认为情况正好相反。如果构造函数检查并阻止 null 值,那么如果 setter 接受它,我会觉得这是一个错误。
    • @Joachim Sauer - 同意,我刚刚删除了这部分
    • 在 OP 的示例中,setter 不是最终的,这将允许子类违反约束。与来自构造函数的所有调用一样,这些方法应该是最终的或私有的。
    • 仇恨制造者。全部用于不可变对象。
    • 现在您可以使用 Guava 的惊人库来做到这一点。查找“checkArgument ()”。检查此链接以进行两者之间的比较:piotrjagielski.com/blog/…(编辑:刚刚意识到在讨论中提到了番石榴,但没有提供很好比较的链接)
    【解决方案8】:

    我会有一个实用方法:

     public static <T> T checkNull(String message, T object) {
         if(object == null) {
           throw new NullPointerException(message);
         }
         return object;
      }
    

    我希望它返回对象,以便您可以在这样的分配中使用它:

     public Constructor(Object param) {
         this.param = checkNull("Param not allowed to be null", param);
     }
    

    编辑:关于使用第三方库的建议,谷歌先决条件尤其比我的代码做得更好。但是,如果这是将库包含在您的项目中的唯一原因,我会犹豫不决。方法太简单了。

    【讨论】:

    • 我相信Objects.notNull是为JDK7提议的。
    • 其实是实现了 Objects.requireNonNull(T obj) 或者 Objects.requireNonNull(T obj, String message)。第二个抛出空指针异常。
    【解决方案9】:

    抛出未经检查的异常的另一种方法是使用assert。否则我会抛出检查异常以使调用者意识到构造函数不会使用非法值。

    您的前两个解决方案之间的区别 - 您是否需要详细的错误消息,您是否需要知道哪个参数失败或者是否足以知道由于非法参数而无法创建实例?

    请注意,第二个和第三个示例无法正确报告两个参数都为空。

    顺便说一句 - 我投票支持 (1) 的变体:

    if (one == null || two == null) {
        throw new IllegalArgumentException(
          String.format("Parameters can't be null: one=%s, two=%s", one, two));
    }
    

    【讨论】:

    • 在这种情况下,null 是程序员错误,调用者可以在调用构造函数之前进行检查。因此,我不认为它是检查异常的合适候选者。
    【解决方案10】:

    您可以简单地拥有一个方法,该方法接受您需要验证的所有构造函数参数。根据哪个参数无效,此方法会抛出带有特定消息的异常。 你的构造函数调用这个方法,如果它通过了,它会初始化值。

    【讨论】:

    • 当然可以,但这会优于上面列出的任何样式吗?
    • 如果你的对象实例永远不应该有空字段,那就不会更好了。在这种情况下,Bozho 的答案很棒,即使用您的第三个解决方案。如果这些字段可以为空,但不能在实例化时,实际上这只是您的构造函数,应该进行完整性检查,在这种情况下,仅由构造函数调用的单独方法将是理想的,更易于维护并保持构造函数代码整洁。
    猜你喜欢
    • 2020-06-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-03
    • 2013-01-31
    • 2015-07-25
    • 2017-06-12
    • 2020-05-13
    • 2013-08-20
    相关资源
    最近更新 更多