java 避免空指针 java参数判空 - 避免!= null语句




15 Answers

这对我来说听起来像是一个相当普遍的问题,初级到中级开发人员往往会在某些方面面临这样的问题:他们要么不知道,要么不信任他们参与的合同,并且防御性地过度检查空值。 另外,在编写自己的代码时,它们倾向于依赖返回空值来指示某些东西,从而要求调用者检查空值。

换句话说,有两个实例进行空检查:

  1. 如果null是合同方面的有效回复; 和

  2. 哪里不是有效的回复。

(2)很容易。 使用assert语句(断言)或允许失败(例如, NullPointerException )。 断言是1.4中添加的高度未充分利用的Java功能。 语法是:

assert <condition>

要么

assert <condition> : <object>

其中<condition>是一个布尔表达式, <object>是一个对象,其toString()方法的输出将包含在错误中。

如果条件不为真,则assert语句抛出ErrorAssertionError )。 默认情况下,Java会忽略断言。 您可以通过将选项-ea传递给JVM来启用断言。 您可以为各个类和包启用和禁用断言。 这意味着您可以在开发和测试时使用断言验证代码,并在生产环境中禁用它们,尽管我的测试表明,断言没有性能影响。

在这种情况下不使用断言是可以的,因为代码只会失败,如果使用断言将会发生这种情况。 唯一的区别是,断言可能会更快地发生,以更有意义的方式发生,并且可能还有额外的信息,这可能会帮助您弄清楚如果您不期望它发生的原因。

(1)有点难。 如果您无法控制您正在调用的代码,那么您就会陷入困境。 如果null是有效响应,则必须检查它。

如果它是你控制的代码(然而通常就是这种情况),那么这是一个不同的故事。 避免使用空值作为响应。 使用返回集合的方法,很容易:几乎一直返回空集合(或数组)而不是null。

对于非收藏,它可能更难。 以此为例:如果您有这些接口:

public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

Parser采用原始用户输入并找到要做的事情,也许是在为某些事情实现命令行界面时。 现在,如果没有适当的操作,您可以使合同返回null。 这导致你正在谈论的空检查。

另一种解决方案是永远不会返回null,而是使用Null Object模式

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

相比:

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

ParserFactory.getParser().findAction(someInput).doSomething();

这是一个更好的设计,因为它导致更简洁的代码。

也就是说,也许findAction()方法完全适合抛出带有意义错误消息的异常 - 特别是在你依赖用户输入的情况下。 对于findAction方法来说,抛出一个Exception比使用一个简单的NullPointerException而没有解释的调用方法要好得多。

try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

或者,如果您认为try / catch机制太难看,而不是Do Nothing,则默认操作应该向用户提供反馈。

public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}
8判空 java多线程空指针

我使用object != null来避免NullPointerException

有没有一个很好的替代品呢?

例如:

if (someobject != null) {
    someobject.doCalc();
}

当不知道对象是否为null时,这可以避免NullPointerException

请注意,接受的答案可能已过期,请参阅https://.com/a/2386013/12943以获取更新的方法。




如果不允许空值

如果您的方法是外部调用的,请从以下内容开始:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

然后,在该方法的其余部分中,您将知道该object不为null。

如果它是一个内部方法(不是API的一部分),只需记录它不能为null,就是这样。

例:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

但是,如果您的方法只是传递了值,并且下一个方法将其传递给它,则可能会出现问题。 在这种情况下,您可能需要检查上面的参数。

如果允许null

这真的取决于。 如果发现我经常做这样的事情:

if (object == null) {
  // something
} else {
  // something else
}

所以我分支,做两件完全不同的事情。 没有丑陋的代码片段,因为我真的需要根据数据做两件事。 例如,我应该处理输入,还是应该计算一个好的默认值?

我很少使用成语“ if (object != null && ... ”)。

如果您显示通常使用成语的示例,则可能更容易为您提供示例。




如果不允许使用未定义的值:

您可以配置IDE以警告您可能存在空取消引用。 例如,在Eclipse中,请参阅首选项> Java>编译器>错误/警告/空分析

如果允许未定义的值:

如果要定义未定义值有意义的新API ,请使用选项模式 (可能熟悉函数式语言)。 它具有以下优点:

  • API中明确说明输入或输出是否存在。
  • 编译器强制您处理“未定义”的情况。
  • Option是monad ,因此不需要详细的null检查,只需使用map / foreach / getOrElse或类似的组合器来安全地使用该值(example)

Java 8有一个内置的Optional类(推荐); 对于早期版本,有库替代品,例如GuavaOptionalFunctionalJavaOption 。 但是与许多功能样式模式一样,在Java中使用Option(甚至8)会产生相当多的样板,使用较简洁的JVM语言(例如Scala或Xtend)可以减少这种样板。

如果你必须处理可能返回null的API ,那么你在Java中做不了多少。 Xtend和Groovy有Elvis运算符 ?:null-safe dereference运算符 ?. ,但请注意,如果是空引用,则返回null,因此它只是“延迟”对null的正确处理。




使用Java 8,新的java.util.Optional类可以解决一些问题。 至少可以说它提高了代码的可读性,在公共API的情况下,使API的合同更清晰。

他们的工作方式如下:

创建给定类型( Fruit )的可选对象作为方法的返回类型。 它可以为空或包含Fruit对象:

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for (Fruit fruit : fruits) {
      if (fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

现在看一下我们搜索给定Fruit实例的Fruitfruits )列表的代码:

Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}

您可以使用map()运算符对可选对象执行计算或从中提取值。 orElse()允许您为缺失值提供回退。

String nameOrNull = find("lemon", fruits)
    .map(f -> f.getName())
    .orElse("empty-name");

当然,检查null / empty值仍然是必要的,但至少开发人员意识到该值可能是空的并且忘记检查的风险是有限的。

在每次返回值可能为空时使用Optional构建的API中,只有当它不能为null (约定)时才返回普通对象,客户端代码可能会放弃对简单对象返回值的空检查...

当然Optional也可以用作方法参数,在某些情况下,可能是指示可选参数而不是5或10重载方法的更好方法。

Optional提供了其他方便的方法,例如允许使用默认值的ifPresent ,以及使用lambda表达式的 ifPresent

我邀请您阅读本文(我写这个答案的主要来源),其中NullPointerException (以及一般的空指针)有问题以及Optional带来的(部分)解决方案得到了很好的解释: Java可选对象




  • 如果你认为一个对象不应该为null(或者它是一个bug),请使用一个断言。
  • 如果你的方法不接受null params在javadoc中说它并使用断言。

只有当你想处理对象可能为null的情况时,你必须检查对象!= null ...

有人建议在Java7中添加新的注释来帮助null / notnull params: http://tech.puredanger.com/java7/#jsr308 ://tech.puredanger.com/java7/#jsr308




Google集合框架提供了一种实现空检查的优雅方式。

像这样的库类中有一个方法:

static <T> T checkNotNull(T e) {
   if (e == null) {
      throw new NullPointerException();
   }
   return e;
}

用法是(使用import static ):

...
void foo(int a, Person p) {
   if (checkNotNull(p).getAge() > a) {
      ...
   }
   else {
      ...
   }
}
...

或者在你的例子中:

checkNotNull(someobject).doCalc();



而不是Null对象模式 - 它有其用途 - 你可能会考虑null对象是一个bug的情况。

抛出异常时,检查堆栈跟踪并完成错误。




Null不是'问题'。它是complete建模工具集的组成部分。软件旨在模拟世界的复杂性,而null则承担其负担。Null表示 Java等中的“无数据”或“未知”。因此,为这些目的使用null是合适的。我不喜欢'Null对象'模式;我认为它会提升谁来守护守护者 ”的问题。
如果你问我女朋友的名字我会告诉你我没有女朋友。在Java语言中,我将返回null。另一种方法是抛出有意义的例外来表明一些不可能的问题(或者不要我希望在那里解决并将其委托给堆栈中较高的位置,以重试或向用户报告数据访问错误。

  1. 对于“未知问题”,请给出“未知答案”。(从业务角度来看,这是正确的,这是正确的)在使用之前在方法内检查一次null参数会减少多个调用者在调用之前检查它们。

    public Photo getPhotoOfThePerson(Person person) {
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    }
    

    上一步导致正常的逻辑流程,从我的照片库中得不到任何不存在的女朋友的照片。

    getPhotoOfThePerson(me.getGirlfriend())
    

    它适合新的Java API(期待)

    getPhotoByName(me.getGirlfriend()?.getName())
    

    虽然对于某些人来说,不是为了找到存储在数据库中的照片而是“正常的业务流程”,但我过去常常使用如下所示的对

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);
    

    并且不要厌恶键入<alt> + <shift> + <j>(在Eclipse中生成javadoc)并为您的公共API编写三个额外的单词。对于那些没有阅读文档的人来说,这将是绰绰有余的。

    /**
     * @return photo or null
     */
    

    要么

    /**
     * @return photo, never null
     */
    
  2. 这是理论上的情况,在大多数情况下,您应该更喜欢java null safe API(如果它将在另外10年内发布),但它NullPointerException是一个的子类Exception因此,它Throwable表示合理的应用程序可能想要捕获的条件(javadoc)!为了使用异常的第一个最大优势和从“常规”代码中分离错误处理代码(根据Java的创建者),对我来说,抓住它是合适的NullPointerException

    public Photo getGirlfriendPhoto() {
        try {
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
        } catch (NullPointerException e) {
            return null;
        }
    }
    

    可能会出现问题:

    问:如果getPhotoDataSource()返回null会怎么样?
    答:这取决于业务逻辑。如果我找不到相册,我会告诉你没有照片。如果未初始化appContext怎么办?这种方法的业务逻辑就是这样。如果相同的逻辑应该更严格,那么抛出异常它就是业务逻辑的一部分,应该使用null的显式检查(情况3)。在新的Java空安全的API更适合的位置,指定选择性意味着什么,什么不意味着被初始化是快速失败的编程错误的情况下。

    问:可以执行冗余代码并且可以获取不必要的资源。
    答:如果getPhotoByName()尝试打开数据库连接,最后创建PreparedStatement并使用人名作为SQL参数,则可能会发生这种情况。针对未知问题的方法给出了未知答案(案例1)。在获取资源之前,该方法应检查参数并在需要时返回“未知”结果。

    问:由于尝试关闭开放,这种方法会带来性能损失。
    答:首先应该易于理解和修改软件。只有在此之后,才能考虑性能,只有在需要时!并在需要的地方!(source)和许多其他人)。

    PS。这种方法使用起来也是合理的,因为“常规”代码原则的单独错误处理代码在某些地方使用是合理的。考虑下一个例子:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        try {
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
        } catch (NullPointerException e) {
            return null;
        }
    }
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        SomeValue result = null;
        if (predicate != null) {
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) {
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) {
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) {
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) {
                            result = result4.getSomeProperty();
                        }
                    }
                }
            }
        }
        return result;
    }
    

    PPS。对于那些快速下载(并且读取文档不那么快)的人,我想说我的生活中从未发现过空指针异常(NPE)。但这种可能性是由Java创建者故意设计的,因为NPE是其子类Exception。我们在Java历史上有一个先例,当时ThreadDeathError不是因为它实际上是一个应用程序错误,而仅仅是因为它不打算被抓住!多少NPE适合成为一个ErrorThreadDeath!但事实并非如此。

  3. 仅在业务逻辑暗示时才检查“无数据”。

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) {
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
        } else {
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        }
    }
    

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    }
    

    如果未初始化appContext或dataSource,则未处理的运行时NullPointerException将Thread.defaultUncaughtExceptionHandler当前线程并将由Thread.defaultUncaughtExceptionHandler处理(您可以定义和使用您喜欢的记录器或其他通知机制)。如果未设置,ThreadGroup#uncaughtException将stacktrace打印到系统错误。应该监视应用程序错误日志并为每个未处理的异常打开Jira问题,这实际上是应用程序错误。程序员应该在初始化的东西中修复bug。




Java中常见的“问题”确实存在。

首先,我对此的看法:

我认为在传递NULL时“吃”某些东西是不好的,其中NULL不是有效值。如果你没有以某种错误退出该方法,那么这意味着你的方法没有出错,这是不正确的。然后你可能在这种情况下返回null,并且在接收方法中你再次检查null,它永远不会结束,你最终得到“if!= null”等。

因此,IMHO,null必须是一个严重的错误,它会阻止进一步执行(即null不是有效值)。

我解决这个问题的方法是这样的:

首先,我遵循这个惯例:

  1. 所有公共方法/ API总是检查其参数是否为null
  2. 所有私有方法都不检查null,因为它们是受控方法(如果上面没有处理的话,只需使用nullpointer异常)
  3. 唯一不检查null的其他方法是实用程序方法。它们是公开的,但如果你出于某种原因打电话给它们,你就知道你传递了什么参数。这就像试图在没有提供水的情况下在水壶中煮水...

最后,在代码中,public方法的第一行如下:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

请注意,addParam()返回self,因此您可以添加更多参数进行检查。

如果任何参数为null,validate()则抛出方法将被检查ValidationException(选中或取消选中更多是设计/品味问题,但我ValidationException的选中)。

void validate() throws ValidationException;

例如,如果“plans”为null,则该消息将包含以下文本:

参数[plans]遇到非法参数值null

正如您所看到的,用户消息需要addParam()方法(字符串)中的第二个值,因为您无法轻松检测传入的变量名称,即使使用反射(无论如何都不是此帖子的主题......)。

是的,我们知道超出这一行我们将不再遇到空值,所以我们只是安全地调用这些对象上的方法。

这样,代码就干净,易于维护和读取。




除了使用之外,assert您还可以使用以下内容:

if (someobject == null) {
    // Handle null here then move on.
}

这略好于:

if (someobject != null) {
    .....
    .....



    .....
}



Guava是Google非常有用的核心库,它有一个很好的有用API来避免空值。我发现Guava非常有帮助。

如维基中所述:

Optional<T>是一种用非空值替换可空T引用的方法。Optional可以包含非空T引用(在这种情况下我们说引用是“存在”),或者它可以不包含任何东西(在这种情况下我们说引用是“不存在”)。它永远不会被称为“包含空”。

用法:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5



我喜欢Nat Pryce的文章。以下是链接:

在文章中还有一个指向Java Maybe Type的Git存储库的链接,我发现它很有趣,但我不认为它可以减少检查代码的膨胀。在对互联网进行一些研究之后,我认为!=空代码膨胀主要是通过精心设计来减少的。




Java 8或更新版本的最佳替代方案可能是使用Optional该类。

Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);

对于可能为空的值的长链,这尤其方便。 例:

Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
    .map(f -> f.getBar())
    .map(b -> b.getBaz())
    .map(b -> b.getInt());

有关如何在null上抛出异常的示例:

Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);

Java 7引入了Objects.requireNonNull一种方法,当检查某些内容是否为非null时,该方法很方便。例:

String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();



我高度忽略了建议在每种情况下使用null对象的答案。这种模式可能会破坏合同并将问题深入和深入地埋没而不是解决它们,没有提到使用不当会产生另一堆样板代码,这将需要将来的维护。

实际上,如果从方法返回的内容可以为null并且调用代码必须对此做出决定,那么应该有一个确保状态的早期调用。

还要记住,如果毫无顾虑地使用空对象模式将是内存饥饿。为此 - 应该在所有者之间共享NullObject的实例,而不是每个实例的unigue实例。

此外,我不建议使用此模式,其中类型是基本类型表示 - 如数学实体,不是标量:向量,矩阵,复数和POD(普通旧数据)对象,它们旨在保持状态以Java内置类型的形式。在后一种情况下,您最终会调用具有任意结果的getter方法。例如,NullPerson.getName()方法应返回什么?

值得考虑这些案例以避免荒谬的结果。




这是大多数开发人员最常出现的错误。

我们有很多方法可以解决这个问题。

方法1:

org.apache.commons.lang.Validate //using apache framework

notNull(Object object,String message)

方法2:

if(someObject!=null){ // simply checking against null
}

方法3:

@isNull @Nullable  // using annotation based validation

方法4:

// by writing static method and calling it across whereever we needed to check the validation

static <T> T isNull(someObject e){  
   if(e == null){
      throw new NullPointerException();
   }
   return e;
}



Related