如何处理 Java 可选链中空值的日志记录

假设我正在使用选项,并且我有:


A a;

....

Optional.ofNullable(a)

    .map(A::getB)

    .map(B::getC)

    .ifPresent(c -> ...) // do something with c

但是,如果出于某种原因,我想在 B::getC 为空(或 a 为空)时记录。有一些成语来处理这个问题吗?我可以做一堆嵌套的 ifPresent 调用,但这非常冗长。


我能想到的最好的方法是一个 FunctionalHelper 类,它包装了一些 Optional 方法并添加了其他方法来支持日志记录(log4j):


private static final Logger LOG = //...

private static final FunctionalHelper FH 

    = new FunctionalHelper(LOG, Level.DEBUG);


FH.ofNullable(a, Level.WARN, "You gave me a null A"))

    .map(FH.logNull(A::getB, "b was null for a: {}", a-> FH.args(a.getName()))

    .map(B::getC)

    .ifPresent(c -> ...) // do something with c

它有效,但感觉有点像一个螺栓固定的解决方案。是否有一些成语(或至少是标准库)来处理这种事情?


(我也想知道如何干净地处理链中抛出的检查异常,但也许这是一个单独的问题。)


更新:作为对@Ole VV 的回应,这里是可选的示例(更新2:我对其进行了一些调整以匹配 log4j varags 供应商语义,但具有功能:


private static final FunctionalHelper FH  = new FunctionalHelper(LOG, Level.DEBUG);


void foo(A someA) {

    FH.ofNullable(someA, Level.WARN, "You gave me a null A")

            .map(FH.logNull(A::getB, "B was null for A: {}", A::getName))

            .map(FH.logNull(B::getC, "C was null for B: {}", B::getName))

            .ifPresent(c -> { /* do something with c */});

}

这是一个 If-else 实现:


void foo2(A a) {

    if (a == null) LOG.debug("You gave me a null A");

    else {

        B b = a.getB();

        if (b == null) LOG.debug("B was null for a: {}", a::getName);

        else {

            String c = b.getC();

            if (c == null) LOG.debug("C was null for a: {}", b::getName);

            else { /* do something with c */ }

        }

    }

}

我会说前者更容易阅读。也更容易修改现有的可选链以在需要时添加一些东西。


哆啦的时光机
浏览 131回答 4
4回答

桃花长相依

正如@Ole 所说, Optional 并不意味着:与其创建一个 Optional,不如创建一个单例流(我刚刚注意到 Stream::of 的存在)。您失去了 Optional::map 仅在元素存在时执行的便利性,因此您自己有过滤器空值:A a;Stream.of(a)&nbsp; .peek(e->{if(e == null){LOG.debug("You gave me a null A");}})&nbsp; .filter(e->e != null) //Now you have to handle null values yourself&nbsp; .map(A::getB)&nbsp; .peek(e->{if(e == null){LOG.debug("B was null for A "+a.getName());}})&nbsp; .filter(e->e != null)&nbsp; .map(B::getC)&nbsp; .peek(e->{if(e == null){LOG.debug("C was null for B"+a.getB().getName());}})&nbsp; .filter(e->e != null)&nbsp; .findFirst() // Optional from now on.如果您从可选开始,您可以执行以下操作:Optional<A> optA;Stream.of(optA.orElse(null))stream 到 optional 的过渡是平滑的,而 optional 到 stream 则不是。

心有法竹

你不应该使用这样的结构有两个原因:首先,你不应该混搭nulland&nbsp;Optional。Optional真的是nullapi-wise 的替代品。当你返回时null,你就有问题了。A::getB应该返回一个Optional<B>,而不是一个null。好的,有时您可以从外部世界获取 API,而您没有动手A::getB,所以如果是这种情况,您只需像这样工作,但另一种方法是清理您的输入。其次,您不应该记录null值。如果你记录null值,这意味着你有类似的问题NullPointerExceptions,这意味着你没有得到你所期望的。所以这基本上意味着你应该清理你的输入!在开发的时候有这种语句是可以的,但是在生产中,你应该能够在a.getB()返回null和b.getC()返回null时区别对待。这些天来,使用调试器通常比使用那些关于哪个值是null.这两个问题都可以通过清理您的输入来解决。清理您的输入意味着您应该将不属于您的输入映射到您的输入。通常的答案是代码重复:API 代码和您的代码。嗯,是的:有 API 模型和你的模型,但它不是重复的代码:一个被清理,另一个没有!但无论如何,使用简单的包装器完全可以实现您的期望。实际上,它与您编写的内容相似(即使您没有显示代码),但我想您已经找到了处理此问题的最佳方法:static <T,U> Function<T, U> logIfReturnsNull(Function<? super T, ? extends U> function, String functionName) {&nbsp; return input -> {&nbsp; &nbsp; U result = function.apply(input);&nbsp; &nbsp; if (result == null) {&nbsp; &nbsp; &nbsp; log.debug("{} returned null for input {}", functionName, input);&nbsp; &nbsp; }&nbsp; &nbsp; return result;&nbsp; };}然后你像这样使用它:Optional.ofNullable(a)&nbsp; .map(logIfReturnsNull(A::getB, "A::getB"))&nbsp; .map(logIfReturnsNull(B::getC, "B::getC"))&nbsp; .ifPresent(c -> ...)

慕勒3428872

您可以与返回一些默认值Optional.orElseGet()的 a 一起使用(不确定我的类型是否完全正确)。Supplier<A>ASupplier<A> nonNullASupplier = () -> {&nbsp; &nbsp; LOGGER.log("Invoking supplier because A was null");&nbsp; &nbsp; return new A();}然后在调用代码中:Optional.ofNullable(a)&nbsp; &nbsp; .orElseGet(nonNullASupplier)&nbsp; &nbsp; .map(A::getB)&nbsp; &nbsp; .map(B::getC)&nbsp; &nbsp; .ifPresent(c -> ...) // do something with c同样不是 100% 确定这里有什么类型A,B但这是基本思想。

暮色呼如

我不知道这是否适合你,但也许你可以使用空对象模式。class NullA extends A {&nbsp; B getB() {&nbsp; &nbsp; &nbsp;return new NullB();&nbsp; }}class NullB extends B {&nbsp; C getC() {&nbsp; &nbsp; &nbsp;return new NullC();&nbsp; }}class NullC extends C {}然后使用orElseGet返回空对象:Optional.ofNullable(a)&nbsp; &nbsp; .orElseGet(new NullA())&nbsp; &nbsp; .map(A::getB)&nbsp; &nbsp; .map(B::getC)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java