gpt4 book ai didi

java - 缓存方法导致不可变对象(immutable对象)

转载 作者:塔克拉玛干 更新时间:2023-11-03 04:14:13 24 4
gpt4 key购买 nike

假设我有一个表示复数的简单接口(interface),它的实例是不可变的。为了简洁起见,我省略了明显的 plusminustimesdivide 方法,它们只会创建并返回一个新的不可变实例。

public interface Complex {

double real();

double imaginary();

double absolute();

double angle();

}

现在的问题是,将其实现为不可变类的最佳方法是什么?最简单直接的“我只在性能出现问题时才关心它”的方法是将实部和虚部存储为最终字段,并在每次调用这些方法时计算绝对值和角度。这使类保持小而简单,但显然最后两个方法每次都返回相同的结果。

public final class NonCachingComplex implements Complex {

private final double real;
private final double imaginary;

public NonCachingComplex(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
}

@Override public double real() {
return real;
}

@Override public double imaginary() {
return imaginary;
}

@Override public double absolute() {
return Math.sqrt((real * real) + (imaginary * imaginary));
}

@Override public double angle() {
return absolute() == 0 ? 0 : (Math.acos(real / absolute()) * Math.signum(imaginary));
}
}

那么为什么不在创建时将绝对值和角度保存到字段中呢?好吧,显然类的内存占用现在有点大,而且,如果很少调用这两个方法,则计算每个创建的实例的结果也可能会适得其反。

public final class EagerCachingComplex implements Complex {

private final double real;
private final double imaginary;

private final double absolute;
private final double angle;

public EagerCachingComplex(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
this.absolute = Math.sqrt((real * real) + (imaginary * imaginary));
this.angle = absolute == 0 ? 0 : (Math.acos(real / absolute()) * Math.signum(imaginary));
}

// real() and imaginary() stay the same...

@Override public double absolute() {
return absolute;
}

@Override public double angle() {
return angle;
}
}

我想到的第三种可能性是在第一次需要时懒惰地计算绝对值和角度。但是正如您所看到的,这使得代码有点困惑并且容易出错。另外,我不确定在这种情况下使用 volatile 修饰符是否真的正确。

public final class LazyCachingComplex implements Complex {

private final double real;
private final double imaginary;

private volatile Double absolute;
private volatile Double angle;

public LazyCachingComplex(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
}

// real() and imaginary() stay the same...

@Override public double absolute() {
if (absolute == null) {
absolute = Math.sqrt((real * real) + (imaginary * imaginary));
}
return absolute;
}

@Override public double angle() {
if (angle == null) {
angle = absolute() == 0 ? 0 : (Math.acos(real / absolute()) * Math.signum(imaginary));
}
return angle;
}

}

所以我的问题是,这三种方法中哪种最好?还有其他更好的方法吗?我是否应该关心性能并坚持第一种方法,只有在性能成为真正的问题时才考虑优化?

最佳答案

我几乎每次都会选择 NonCachingComplex。

原因:

  • 这是最简单的 - 所以你应该先这样写,只有当你通过基准测试证明这是必要的时候才让事情变得更复杂。避免过早优化等等!
  • 用于计算 absolute() 和 angle() 的公式可能不足以证明缓存的合理性。现代 CPU 上的浮点运算非常快,通常比从内存中获取值更快。
  • 最低内存占用 - 这不仅有利于减少代码的整体内存消耗,而且还可以提高性能,因为更多数据将适合高速处理器缓存。对于某些工作集大小,这可能会产生很大的不同。

在其他方面,LazyCachingComplex 特别糟糕,因为它对绝对值和角度使用盒装值(这意味着要访问的额外内存解引用,加上两个额外的对象开销)。我认为这样做不太可能看到性能上的好处。

请注意,如果您真的关心性能,那么您也不会使用 Complex 接口(interface) - 最好的性能是直接创建一个最终的 Complex 类,并在您的代码中直接引用该类.通过接口(interface)调用方法的成本(略)高于最终类上的方法调用。

关于java - 缓存方法导致不可变对象(immutable对象),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9401373/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com