以Java 8方式检查对象中包含的空对象和空值

Posted

技术标签:

【中文标题】以Java 8方式检查对象中包含的空对象和空值【英文标题】:Check for null object and null value contained in object in Java 8 way 【发布时间】:2018-05-11 16:38:38 【问题描述】:

如何用 Optionals 重写这个函数,使其更像 Java 8?还是我应该保持原样?

public void setMemory(ArrayList<Integer> memory) 
    if (memory == null)
        throw new IllegalArgumentException("ERROR: memory object can't be null.");
    if (memory.contains(null))
        throw new IllegalArgumentException("ERROR: memory object can't contain null value.");

    this.memory = memory;

【问题讨论】:

如果您仍想抛出两个不同的自定义异常,我不知道如何缩短此时间。恕我直言,这很好。 我更愿意通过将前置条件作为 Javadoc 注释添加到参数而不是对空值进行 O(n) 检查,从而将责任(按合同设计)交给方法调用者。跨度> @shinjw Objects.requireNonNull? @shinjw 我同意,Optionals 会使事情复杂化,尤其是第二次检查甚至不是空检查。一些答案显示了使这些检查更具表现力的其他方法。 您应该知道,当您存储对调用者提供的集合的引用而不复制时,没有什么可以阻止调用者添加null调用setMemory之后。 【参考方案1】:

你有一个模式 condition -&gt; throw an exception 可以移动到一个方法中:

private void checkOrElseThrow(boolean condition, Supplier<? extends RuntimeException> exceptionSupplier) 
    if (condition) 
        throw exceptionSupplier.get();
    


public void setMemory(List<Integer> memory) 

    checkOrElseThrow(memory == null, () -> new IllegalArgumentException("message #1"));
    checkOrElseThrow(memory.contains(null), () -> new IllegalArgumentException("message #2"));

    this.memory = memory;

如果不改变异常的类型,只传递异常的消息是合理的(感谢@tobias_k指出):

private void checkOrElseThrow(boolean condition, String exceptionMessage) 
    if (condition) 
        throw new IllegalArgumentException(exceptionMessage);
    


public void setMemory(List<Integer> memory) 

    checkOrElseThrow(memory == null, "message #1");
    checkOrElseThrow(memory.contains(null), "message #2");

    this.memory = memory;

【讨论】:

checkAndThrow 应该采用谓词 IMO @Michael,它将添加一个通用参数和一个附加参数。此外,将始终计算条件 如果 OP 真的想更改代码,我更喜欢这个答案。示例中的 null 检查已经足够好,但这种方法可能被认为更具表现力,尤其是在整个应用程序中使用它时。查看尝试使用选项做某事的答案,我并没有看到太多好处,因为有些检查甚至不是空检查(如示例中的包含检查)。 如果 OP 希望在两种情况下都抛出相同类型的异常,您也可以只将错误消息传递给方法而不是供应商。现在它并不比 OP 更短或更易读,只是在一行中。 感谢另一种编写此功能的方法,但我认为与 if + throw 相比,采用 bool 表达式和字符串消息的新方法有点矫枉过正。我可能错了,还在学习 Java,但这就是我的想法。【参考方案2】:

如果你想坚持IllegalArgumentException 并且你在类路径上有番石榴,你可以使用这个:

Preconditions.checkArgument(memory != null, 
            "ERROR: memory object can't be null.");
Preconditions.checkArgument(!memory.contains(null), 
            "ERROR: memory object can't contain null value.");

您不能在此处真正使用Optional,因为您需要针对不同条件的不同错误消息。

另一方面,如果您可以接受一条错误消息,您可以这样做:

this.memory = Optional.ofNullable(memory)
            .filter(x -> !x.contains(null))
            .orElseThrow(() -> new IllegalArgumentException(
                         "memory object is null or contains null values"));

【讨论】:

我不认为需要 2 个不同的错误消息(如果有,则应重新考虑),所以我认为您的 Optional 示例是表达这一点的最佳方式。跨度> @Michael 恕我直言,没办法。假设在这个确切的版本中访问源代码,那么知道究竟是什么问题比任何消息更重要。缺少/不足的消息使我检查版本,将堆栈跟踪复制到 IDE 并单击。不知道对象本身或其内容是否为 null 使我不得不考虑(最多)两倍的原因。 @maaartinus 同意,我个人遇到的问题是 OP 抛出的是 IAE 而不是 NPE - 这对我来说会更好 @Eugene 我也会投票给 NPE,但我并不在乎。实际上应该有一个扩展两者的INAE,但我们不能拥有它。我们可以有一个INAE extends NPE,但我不是介绍它的人。 :D +++ 我更喜欢 any hack 将这两个原因合二为一,包括 ImmmutableList.copyOf(memory)(简短、简洁和愚蠢;不满足 OP 的要求)。 @maaartinus 然后只需将ofNullable 更改为of - 如果列表为空,则为 NPE,如果列表包含空,则为 IAE。【参考方案3】:

对于第一种情况,我会使用: Objects.requireNonNull()

我不认为Optional 是一种方法,因为null 是非法值。

【讨论】:

这将抛出一个NullPointerException,可能不是 OP 想要的 我冒昧地假设 OP 基于 object can't be null 错误消息想要这种行为:) a require-non-null 正是 OP 的意图。为了防止以后发生 NPE,抛出 NPE 是一种正确(不正当)的快速失败策略。 我总是更喜欢不需要解析消息来找出发生了什么的异常类型,即NullPointerException。可能值得为 OP 的用例添加实际示例,即 Objects.requireNonNull(memory, "ERROR: memory object can't be null.");memory.forEach(o -&gt; Objects.requireNonNull(o, "ERROR: memory object can't contain null value."));,恕我直言,与巴洛克式的 Optional (ab)use 相比,它们也是最简洁和直接的构造。更不用说性能差异了……【参考方案4】:

在这种情况下,我通常避免使用Optional,因为它往往会掩盖正在发生的事情。

但首先我想提一下,原始代码让调用者保留对现在是包含类的内部字段memory 的引用。也许你相信你的调用者不是恶意的,但调用者可能会不小心重用作为参数传递的列表。如果是这样,尽管进行了细致的参数检查,memory 列表最终可能最终包含空值。或者,它可能会发生意外变化,导致其他故障。

解决方案是制作参数列表的防御性副本。直接的方法如下:

public void setMemory(ArrayList<Integer> memory) 
    if (memory == null)
        throw new IllegalArgumentException("memory is null");

    List<Integer> temp = new ArrayList<>(memory);

    if (temp.contains(null))
        throw new IllegalArgumentException("memory contains null");

    this.memory = temp;

请注意,副本是在检查之前制作并存储在局部变量temp 中的。显然,您不希望在检查列表是否包含空值之前存储到字段中。但是检查是否包含空值应该在副本上完成,而不是在参数列表上,否则,调用者可以在检查之后但在复制之前修改列表。 (是的,这是偏执。)

如果您不关心确切的异常消息,可以将其缩短如下:

public void setMemory(ArrayList<Integer> memory) 
    List<Integer> temp;
    if (memory == null || ((temp = new ArrayList<>(memory)).contains(null)))
        throw new IllegalArgumentException("memory is or contains null");
    this.memory = temp;

现在这个可以重写为使用Optional

public void setMemory(ArrayList<Integer> memory) 
    this.memory = Optional.ofNullable(memory)
                          .map(ArrayList::new)
                          .filter(list -> ! list.contains(null))
                          .orElseThrow(() -> new IllegalArgumentException("memory is or contains null"));

与我经常看到的Optional 的常见滥用行为:-) 相比,这还不算太糟糕。这里的链接用于避免创建局部变量,这有点成功。逻辑相当简单,特别是如果一个人的前脑上有Optional。但是,我有点担心在一个月内重新访问此代码。在说服自己它按照你的意图做之前,你可能不得不眯着眼睛看了一会儿。

最后,几个通用风格的 cmets。

    通常的偏好(至少在 JDK 中)是在这些情况下使用 NullPointerException。对于这些示例,我坚持使用IllegalArgumentException,因为这就是 OP 正在使用的。

    我建议使用 List&lt;Integer&gt; 而不是 ArrayList&lt;Integer&gt; 作为参数类型和可能的字段类型。这将允许在适当的情况下使用不可修改的列表(例如,使用 JDK 9 的List.of)。

【讨论】:

我在关注这个:(***.com/a/47710/4393368)when 选择正确的例外,所以这就是为什么我使用 IAE 而不是 NPE。将参数类型更改为 List 并复制传递的 ArrayList 似乎是个好建议,谢谢。跨度> 关于防御性副本的好点,但你在布尔条件内的赋值杀死了我 :) 这是一个聪明的主意,但为了可读性,我会选择 if-else-assignment-if 当您按照建议使用NullPointerException 并且不关心消息时,就像memory = new ArrayList&lt;&gt;(memory);/* (copies and throws NPE if memory is null) */ memory.forEach(Objects::requireNonNull); this.memory = memory; 一样简单。哦,值得一提的是,参数应该更喜欢抽象类型,即List 而不是ArrayList,尤其是当我们创建一个具有我们想要的类型ArrayList 的防御性副本时。 我不认为复制内存是个好主意。你检查了内存不为空,所以你可以使用memory.contains(null)【参考方案5】:

首先,使用更通用的列表类型作为输入参数可能是个好主意,因此将您的实现更改为:

public void setMemory(List<Integer> memory) 
    //stuff

然后正如其他人提到的那样,为每个“设置”操作检查空值有点过头了。

如果这个“内存列表”来自你的一些代码并且你可以使用番石榴,那么也许使用番石榴不可变列表。当有人尝试将“null”添加到您的列表时,此列表会引发异常。

ImmutableList.of( //your Integers)

如果您不能使用 guava 但仍不想使用该方法,您可以随时编写自己的列表实现来为您执行此 null 检查。

最后,如果您无法实现所有这些,请保持您的代码不变。它很容易阅读,每个人都知道你在做什么。如您在此处的其他答案中所见,使用 Optionals 可能会非常混乱。

【讨论】:

【参考方案6】:

一个带可选的衬垫:

public void setMemory(ArrayList<Integer> memory) 
    this.memory = Optional.ofNullable(memory).map((a) -> Optional.ofNullable(a.contains(null) ? null : a).orElseThrow(() -> new IllegalArgumentException("ERROR: memory object can't contain null value."))).orElseThrow(() -> new IllegalArgumentException("ERROR: memory object can't be null."));

【讨论】:

如果你不使用换行符,一切都是单行的;) 好吧,IDE 自动格式化程序不会打破这一行。叫他们 no-bracerssingle-statementermono-blocker 怎么样:P【参考方案7】:

很抱歉添加了另一个答案,但根据对问题的 cmets 阅读,可能有更好的方法来更改方法的签名:将 ArrayList&lt;Integer&gt; 替换为 IntStream

public void setMemory(@NonNull IntStream input) 
    Objects.requireNonNull(input);

    this.memory = ...; // collect the stream into the storage

原始流不会产生(取消)装箱成本。

这样你就不必担心调用者会改变你脚下的 List 内容,并且可以按照我的other answer 中的说明选择合适的整数存储(甚至懒惰地解析流内容!) .

【讨论】:

假设我会坚持使用 List,intStream.boxed().collect(Collectors.toList()) 不会对将其装入 List 的性能产生负面影响(我的意思是,它必须进行 O(n) 复制,对吗?)然后分配给内存字段?我不明白“原始流不会产生(取消)装箱的成本。”,仍然,您必须从流中一个一个地把它装箱到列表中,或者我弄错了吗?无论如何,感谢您提供另一种方法。我只能补充一点,这个 List 将包含许多小的 ( “我只能补充一点,这个 List 将包含许多小的 ( 【参考方案8】:

不要使用 Optional,它们在这里对你没有好处。

使用更合适的类型代替 ArrayList。在集合中存储整数会产生(取消)装箱成本,并且在不允许空值时没有意义。

可能的馆藏库很少,可以更好地满足您的需求:

    HPPC 集合(我最喜欢,但 API 与 Java 集合框架不兼容) 科洛博克 Fastutil

所有这些库都为原语提供了列表、地图和其他容器的专门实现。这些实现通常比任何涉及 ArrayList&lt;Integer&gt; 的实现都要快得多(除非 ArrayList 中的所有整数都小到足以放入全局 Integer 实例缓存中)。

作为一个很好的副作用,使用原始整数的专用列表默认情况下不允许调用者存储空值。

【讨论】:

【参考方案9】:

仅当您需要两个不同的异常和更多功能样式时,才可以使用我的解决方案。但它看起来很复杂,甚至更长。

.map(e -&gt; false) 将列表元素(在本例中为整数)映射为 filter() 所需的布尔值。

this.memory = Optional.ofNullable(memory)
            .orElseThrow(() -> new IllegalArgumentException("ERROR: memory object can't be null."))
            .stream()
            .filter(element -> 
                    Optional.ofNullable(element)
                    .map(e -> true)
                    .orElseThrow(
                            () -> new IllegalArgumentException("ERROR: memory object can't contain null value.")))
            .collect(Collectors.toList());

【讨论】:

我想知道下一个编写此代码的人会怎么想 @Eugene 也提出了一个问题,为什么如果你要创建一个新的List,你不会只是删除空值并继续。 @Eugene 你是对的,你的解决方案更优雅,但是如果你需要在 null 元素的情况下出现异常的确切消息,就会出现问题 map(e -&gt; false) 是做什么的?你不觉得比原来的sn-p复杂很多吗? @AndrewTobilko 它将列表的元素(在这种情况下为整数)映射到过滤器(方法)所需的布尔值。所以这是一个肮脏的黑客......

以上是关于以Java 8方式检查对象中包含的空对象和空值的主要内容,如果未能解决你的问题,请参考以下文章

休眠,检查对象是不是存在和空值

如何以简单的方式更改堆上 QVector 数组中包含的对象的值?

(转)Java 中关于String的空对象(null) ,空值(empty),空格

数据库中的空值与NULL的区别以及python中的NaN和None

thinkphp的空控制器和空操作以及对应解决方法

Thinkphp的空控制器和空操作以及对应解决方法