Java构造函数样式:检查参数不为null

时间:2010-06-08 13:39:55

标签: java constructor null coding-style

如果您有一个接受某些参数但却不允许其为null的类的最佳做法是什么?

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

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

这里的异常让你知道哪个参数为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");
        }
        //...
     }
  }

哪种款式最好?

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

10 个答案:

答案 0 :(得分:87)

第二个或第三个。

因为它告诉用户您的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还具有保持对象一致性的角色,您也可以选择第三个。

答案 1 :(得分:36)

您可以使用众多库中的一个来促进前置条件检查。 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);

checkNotNullused 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!");
     //...
 }

大多数方法都被重载,无法使用错误消息,修复错误消息或带有varargs的模板化错误消息。


IllegalArgumentException vs NullPointerException

当您的原始代码在IllegalArgumentException个参数上抛出null时,Guava的Preconditions.checkNotNull会抛出NullPointerException

以下是 Effective Java 2nd Edition的引用:第60项:赞成使用标准例外

  

可以说,所有错误的方法调用归结为非法论证或非法状态,但其他例外标准用于非法论证和状态的某些种类。如果调用者在某些禁止空值的参数中传递null,则约定会引发NullPointerException而不是IllegalArgumentException

当您访问NullPointerException引用的成员时,不会保留null;

null

答案 2 :(得分:32)

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

Java 7将java.lang.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的消息,如果第一个参数为空,则抛出该消息(如果你逆转它们,那么你的检查永远不会失败)
  • 另一个最佳实践:如果可能的话,让所有的类成员都成为最终成员(这样你就可以确定:当某个对象被成功创建时,其所有成员都不为空;并且他们不会随着时间的推移而改变)< / LI>

答案 3 :(得分:4)

我会有一个实用方法:

 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);
 }

编辑:关于使用第三方库的建议,特别是Google Preconditions比上面的代码做得更好。但是,如果这是将库包含在项目中的唯一原因,我会犹豫不决。方法太简单了。

答案 4 :(得分:3)

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

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

答案 5 :(得分:2)

检查Java中前提条件的方法比较 - Guava与Apache Commons vs. Spring Framework与普通Java断言

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

答案 6 :(得分:1)

抛出未经检查的异常的替代方法是使用assert。否则我会抛出已检查的异常,以使调用者意识到构造函数不能使用非法值的事实。

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

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

BTW - 我投票支持(1)的变体:

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

答案 7 :(得分:1)

静态分析的注释对于运行时检查的补充或替换也很有用。

例如,

FindBugs提供了@NonNull注释。

  

public SomeClass(@NonNull Object one,@ NonNull Object two){

答案 8 :(得分:0)

您可以使用一个方法来获取验证所需的所有构造函数参数。此方法根据哪个参数无效而抛出特定消息的异常。 构造函数调用此方法,如果它通过,则初始化值。

答案 9 :(得分:0)

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

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

其他人告诉我,企业用户 - 在某些方面 - 是错误的,你引入了更多的依赖 - 对于简单的任务 - 而不是必需的。我也可以接受这一点......但这是我最近的经历:

首先,我编写了自己的私有方法来检查null参数。这很无聊和多余。我知道我应该把它放到一个Utility类中。但是,为什么我应该在第一时间写它,当有人已经完成它?我可以节省时间而不是编写单元测试并设计现有的东西。除非你想锻炼或学习,否则我不建议这样做。

我最近开始使用google的番石榴,我发现 - 和apache commons一起 - 一旦你开始使用它们,你就不会只使用那个单一的方法。你会发现并越来越多地使用它。最后,这将使您的代码更短,更易读,更一致,更易于维护。

顺便说一句:根据你的目标,我会使用上面提到的一个库中的2或3 ...