【问题标题】:Replace get/test/set of bare references with Optional.ofNullable(...).ifPresent(...)?用 Optional.ofNullable(...).ifPresent(...) 替换 get/test/set of bare references?
【发布时间】:2016-10-09 00:06:37
【问题描述】:

我有一个 mutator 方法,它只设置参数中提供的非空字段。该参数返回裸引用,而不是 Optional 包装器,并且无法更改。

在 Java 8 之前,一种方法是:

Double h = arg.getH();
if ( null != h ) setH( h );

Double v = arg.getV();
if ( null != h ) setV( v );

String s = arg.getS();
if ( null != s ) setS( s );

// Etc. ...

从 Java 8 开始,可以用一次性的Optional 更简洁地表达这一点。

Optional.ofNullable( arg.getH()).ifPresent( this::setH );
Optional.ofNullable( arg.getV()).ifPresent( this::setV );
Optional.ofNullable( arg.getS()).ifPresent( this::setS );
// Etc. ...

这个成语不太熟悉。但是,它也消除了潜在的错误来源——例如上面“Java 8 之前”代码中的错误。

问题:频繁使用这种新模式是否有任何负面影响?例如,它与早期模式相比在编译大小或性能方面如何? )

【问题讨论】:

  • 第二个变体创建临时对象,而第一个没有。它们与大多数现实生活中的案例无关,但仍然有开发人员对这些事情感到难过。除此之外,这更多是代码风格的问题。我宁愿专注于这个问题,为什么首先会有Double 对象……
  • 如果代码执行得足够频繁,它可能会达到阈值并被 HotSpot 编译器优化(即内联)。内存影响也不应该那么大,因为可选只是一个包装器,并且很快就会被youngGenGC清理掉。所以我同意 Nicolas,替补!
  • 您问的是性能成本(基本上是两个对象创建)是否值得简洁,但我认为这是错误的问题。就像一个人很容易过分关注性能一样,一个人也可以做到简洁。真正的目标应该是清晰。正如@jpkrohling 所暗示的那样,许多用户会发现Optional 的复杂用法比原始版本的可读性差。
  • @the8472:年轻 GC 的性能取决于 幸存 对象的数量,当您提高 临时 对象的数量时,这不会改变> 对象。考虑到这些临时对象的大小与 TLAB 的大小之比,您只会导致年轻 GC 提前 0.0…% 发生。
  • @AndyThomas Holger 的方法似乎要好得多;它直接解决了意图——获取一个属性,如果它不为空,则将其复制到其他地方。 (它也碰巧产生更少的意外垃圾——未绑定的实例方法引用实际上是常量。)

标签: java java-8 optional


【解决方案1】:

显然,您的任务是调整您的类型的实例以反映您无法控制的类型的属性。 (我有点担心// etc 的注释。)在这种情况下,我们可以将“如果不是null,则转移此属性”封装为自己的操作:

// in MyType

static <T> BiConsumer<TypeOfArg,MyType> transfer(
        Function<TypeOfArg,T> from, BiConsumer<MyType,T> to) {
    return (arg,myself) -> {
        T value = from.apply(arg);
        if(value!=null) to.accept(myself, value);
    };
}
static final BiConsumer<TypeOfArg,MyType> TRANSFER_ALL_PROPERTIES =
    transfer(TypeOfArg::getH, MyType::setH).andThen(
    transfer(TypeOfArg::getV, MyType::setV).andThen(
    transfer(TypeOfArg::getS, MyType::setS)));

void mutatorMethod(TypeOfArg arg) {
    TRANSFER_ALL_PROPERTIES.accept(arg, this);
}

我很确定,这也可能会引起争论,这是否比普通的get-if-set 调用序列(如您的第一个变体)更好,但我认为,这也与熟悉度有很大关系。

对我来说,transfer(TypeOfArg::getV, MyType::setV)Optional.ofNullable( arg.getV()).ifPresent( this::setV ) 更能表达意图,后者读起来很像命令式声明。

对于那些关心临时对象的人,代码不会创建任何对象。

【讨论】:

  • 我还想到辅助方法会更简洁明了,尽管我在考虑像&lt;T&gt; void setMaybe( T val, Consumer&lt;? super T&gt; consumer) { if ( null != val ) consumer.accept(val); } 这样更简单的签名/实现。但我仍然对Optional 方法是否会产生负面影响感兴趣。它只依赖于标准库,而不是引入额外的依赖或冗余代码。关于熟悉的好点。顺便说一句,我很欣赏你之前的 cmets 对性能的影响。
  • 将 setter 表示为 Consumer 需要 this::setX 表单的捕获方法引用。虽然如前所述,临时对象的影响通常被高估,但非捕获解决方案可以让您免于与关注它的开发人员进行讨论。不必讨论它,大大提高了你的效率。 Optional 方法添加了另一个临时的轻量级对象,因此,这也不是性能问题,而是可读性问题。
  • 感谢您对虚拟机和人员的深入了解。 :)
  • 以防万一有人误解“不创建临时对象”描述为“无分配”......这种方法也确实创建了一些对象——从transfer() 返回的 lambda 捕获to and` from, so an allocation is needed for each call to transfer(). But it is far better than the original; each _use_ of the TRANSFER`链不会产生垃圾——它是链的创建涉及一些对象。 (即使只使用一次,它创建的对象也只有 OP 示例的一半。)更重要的是,它更接近 OP 的意图。
【解决方案2】:

以通用的方式回答性能总是很棘手。事实是:JVM 做了如此多的优化,很难说它对你的 应用程序的表现如何。即便如此,随着时间的推移,性能可能会与“冷启动”后测量的性能不同。例如,如果您的参数在 99% 的情况下都不为空,则分支预测可能会使这两个变体的性能变得微不足道。

最后,如果您真的关心性能,请在您自己的应用程序上进行衡量。

总而言之,我会说最好选择您更容易阅读和维护的版本。就我而言,Java 8 之前的版本会胜出,但这只是我 :)

【讨论】:

    猜你喜欢
    • 2017-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-09
    • 2013-01-24
    • 2013-11-14
    • 1970-01-01
    • 2023-03-13
    相关资源
    最近更新 更多