以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) 检查,从而将责任(按合同设计)交给方法调用者。跨度> @shinjwObjects.requireNonNull
?
@shinjw 我同意,Optionals 会使事情复杂化,尤其是第二次检查甚至不是空检查。一些答案显示了使这些检查更具表现力的其他方法。
您应该知道,当您存储对调用者提供的集合的引用而不复制时,没有什么可以阻止调用者添加null
在调用setMemory
之后。
【参考方案1】:
你有一个模式 condition -> 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 -> 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<Integer>
而不是 ArrayList<Integer>
作为参数类型和可能的字段类型。这将允许在适当的情况下使用不可修改的列表(例如,使用 JDK 9 的List.of
)。
【讨论】:
我在关注这个:(***.com/a/47710/4393368)when 选择正确的例外,所以这就是为什么我使用 IAE 而不是 NPE。将参数类型更改为 List 并复制传递的 ArrayList 似乎是个好建议,谢谢。跨度> 关于防御性副本的好点,但你在布尔条件内的赋值杀死了我 :) 这是一个聪明的主意,但为了可读性,我会选择if-else-assignment-if
流
当您按照建议使用NullPointerException
并且不关心消息时,就像memory = new ArrayList<>(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-bracers、single-statementer、mono-blocker 怎么样:P【参考方案7】:很抱歉添加了另一个答案,但根据对问题的 cmets 阅读,可能有更好的方法来更改方法的签名:将 ArrayList<Integer>
替换为 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<Integer>
的实现都要快得多(除非 ArrayList 中的所有整数都小到足以放入全局 Integer
实例缓存中)。
作为一个很好的副作用,使用原始整数的专用列表默认情况下不允许调用者存储空值。
【讨论】:
【参考方案9】:仅当您需要两个不同的异常和更多功能样式时,才可以使用我的解决方案。但它看起来很复杂,甚至更长。
.map(e -> 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 -> false)
是做什么的?你不觉得比原来的sn-p复杂很多吗?
@AndrewTobilko 它将列表的元素(在这种情况下为整数)映射到过滤器(方法)所需的布尔值。所以这是一个肮脏的黑客......以上是关于以Java 8方式检查对象中包含的空对象和空值的主要内容,如果未能解决你的问题,请参考以下文章
如何以简单的方式更改堆上 QVector 数组中包含的对象的值?
(转)Java 中关于String的空对象(null) ,空值(empty),空格