【问题标题】:Lambdas in the classical Operation enum example经典操作枚举示例中的 Lambda
【发布时间】:2014-04-29 09:59:12
【问题描述】:

你们中的许多人可能都知道,Operation 枚举有一个经典示例(不过现在使用 Java 8 标准接口),如下所示:

enum Operation implements DoubleBinaryOperator {
    PLUS("+") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left + right;
        }
    },
    MINUS("-") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left - right;
        }
    },
    MULTIPLY("*") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left * right;
        }
    },
    DIVIDE("/") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left / right;
        }
    };

    private final String symbol;

    private Operation(final String symbol) {
        this.symbol = symbol;
    }

    public String getSymbol() {
        return symbol;
    }
}

测试:

Arrays.stream(Operation.values())
        .forEach(op -> System.out.println("Performing operation " + op.getSymbol() + " on 2 and 4: " + op.applyAsDouble(2, 4)));

它提供:

在 2 和 4 上执行 + 操作:6.0
执行操作 - 在 2 和 4:-2.0
在 2 和 4 上执行操作 *:8.0
在 2 和 4 上执行操作 /:0.5

但我觉得我们可以使用 Java 8 做得更好,因此我实现了以下内容:

enum Operation implements DoubleBinaryOperator {
    PLUS    ("+", (l, r) -> l + r),
    MINUS   ("-", (l, r) -> l - r),
    MULTIPLY("*", (l, r) -> l * r),
    DIVIDE  ("/", (l, r) -> l / r);

    private final String symbol;
    private final DoubleBinaryOperator binaryOperator;

    private Operation(final String symbol, final DoubleBinaryOperator binaryOperator) {
        this.symbol = symbol;
        this.binaryOperator = binaryOperator;
    }

    public String getSymbol() {
        return symbol;
    }

    @Override
    public double applyAsDouble(final double left, final double right) {
        return binaryOperator.applyAsDouble(left, right);
    }
}

在功能上是等价的,但是两个实现是否仍然相似,或者是否有一些隐藏的细节使新版本比旧版本更糟?

最后,从 Java 8 开始,lambda 方式是首选方式吗?

【问题讨论】:

  • Effective Java,第 3 版现在推荐使用这个例子的 lambda 方法。
  • 问题:这里使用枚举是否比具有常量的简单公共类(或内部公共类)有任何好处?即public static final DoubleBinaryOperator PLUS = (l, r) -> l + r; ?
  • @Andrejs 他们有名字并且可以有额外的属性,比如symbol,它允许创建地图。当您免费获得“迭代所有常量”逻辑时,这很顺利。
  • 基本上这是一个正确的application of the strategy pattern

标签: java enums lambda java-8


【解决方案1】:

显然,lambda 版本更具可读性。它不仅更短,而且让读者在构造函数中一眼就能看到实现运算符。想象一下,您还想扩展 enum 以支持 int 计算……

从性能的角度来看,您正在通过生成的 lambda 类交换匿名 enum 内部类。 lambda 版本增加了另一个级别的委托,但这对 HotSpot 优化器没有挑战。执行性能方面不太可能有任何差异。

但是,当应用 lambda 模式时,您可能会获得使用该类的应用程序启动的加速。原因是,对于传统的专用enum 方法,Java 编译器必须为每种情况生成一个内部类,该内部类驻留在文件系统中或(可能是 zip 压缩的)Jar 文件中。动态生成 lambda 类(具有非常简单的结构)的字节码通常比加载类要快。对生成的 lambda 类不进行访问检查也可能会有所帮助。

总结一下:

  • lambda 方法更易于阅读,其代码更易于维护(重点)
  • 执行性能大致相同
  • lambda 方法的启动时间可能会更短

所以这对 lambda 来说是一个巨大的胜利。是的,我认为 lambda 方式是 Java 8 的首选方式。

【讨论】:

    【解决方案2】:

    这取决于你如何定义更好

    在你的情况下,在我看来,lambda 是一个纯粹的胜利。您甚至可以重用一些现有的 JDK 函数,例如:

    enum Operation implements DoubleBinaryOperator {
        PLUS    ("+", Double::sum),
        ...
    }
    

    这是简短易读的。我认为没有任何对代码进行基准测试的性能可以说是合理的。

    Lambdas 使用invokeDynamic 实现,以将调用站点动态链接到要执行的实际代码;没有匿名的内部类。

    【讨论】:

      【解决方案3】:

      我很惊讶没有人提到这一点,但是 lambda 方法在实现多种方法时会变得很麻烦。将一堆无名的 lambda 传递给构造函数会更简洁,但不一定更具可读性。

      此外,使用 lambda 的好处会随着函数大小的增加而减少。如果您的 lambda 长度超过几行,则覆盖可能同样容易阅读,甚至更容易。

      【讨论】:

      • 同意,有效的 Java 第 3 版,第 43 项:lambdas lack names and documentation; if a computation isn't self-explanatory or exceeds a few lines, don't put it in a lambda [...] lambdas of three lines is a reasonable maximum
      【解决方案4】:

      定义更糟,很可能它使用更多的字节码并且速度稍慢。

      除非这些对您很重要,否则我会使用您认为更清晰、更简单的方法。

      【讨论】:

      • 真的会慢吗?我之前在问自己,如果 性能很重要,那么它应该很快内联,此时它的行为与传统解决方案完全相同?除非我弄错了,否则只会留下内存占用。
      • @skiwi 理论上可能是一样的,但是优化器需要做更多的工作,这通常意味着实际性能会降低。 esp 作为单个方法调用比执行的操作要昂贵得多。
      • @bigGuy 字节码会告诉你.class 文件有多大,如果字节码相同,你会得到相同的结果。如果字节码不同(如果不是,我会很惊讶)你需要运行它来看看它有多少不同,如果有的话。
      • 理论上会慢一些,实际上可能是无用的优化。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多