累加器生成器测试 - Java 8

时间:2014-06-06 13:23:15

标签: java java-8 paul-graham

保罗·格雷厄姆在他的伟大文章Revenge of the Nerds中声称,语言的力量各不相同。他提到了一个很好的练习 - 写一个累加器生成器:

  

我们想要编写一个生成累加器的函数 - 一个函数   取一个数字n,并返回另一个函数   编号i并返回n增加i。

Java解决方案

public class Accumulator {

    public interface Inttoint {
        public int call(int i);
    }

    public static Inttoint foo(final int n) {
        return new Inttoint() {
            int s = n;
            public int call(int i) {
                s = s + i;
                return s;
            }};
    }

    public static void main(String... args) {
        Inttoint accumulator = foo(1);

        System.out.println(accumulator.call(2) == 3);
        System.out.println(accumulator.call(3) == 6);
    }

}

我很好奇,无论是在 Java 8 (感谢lambda)已经是一些优雅的方式如何写它类似于Groovy,见下文。我试过了Function<Integer, Integer>

但我坚持这个编译器错误。

  

从lambda表达式引用的局部变量必须是final或   有效的最终

那么你有一些Java 8解决方案吗?

将旧的Java解决方案与Groovy解决方案进行比较

def foo(n) {
    return {n += it}
}

def accumulator = foo(1)
assert accumulator(2) == 3
assert accumulator(3) == 6

1 个答案:

答案 0 :(得分:8)

首先,您仍然可以使用匿名类语法而不是lambda语法来使用所有新的Java 8接口。例如:

import java.util.function.IntUnaryOperator;

public class Accumulator {
    public static IntUnaryOperator foo(int n) {
        return new IntUnaryOperator() {
            private int value = n;
            @Override
            public int applyAsInt(int i) {
                return value += i;
            }
        };
    }

    public static void main(String... args) {
        IntUnaryOperator accumulator = foo(1);
        System.out.println(accumulator.applyAsInt(2)); // output: 3
        System.out.println(accumulator.applyAsInt(3)); // output: 6
    }
}

(而不是Function,我在这里使用IntUnaryOperator因为它允许使用原始int而不是盒装Integer。它在逻辑上等同于{{1如果那是合法的话。)

现在,我们如何用lambda语法缩短这个庞大的东西?传递给lambdas的局部变量必须(有效地)Function<int,int>。限制意味着你不能轻易地写出一个值,它的值在调用之间累积。以下工作:

final

我们可以通过使用一些可变对象作为持有者来解决当前累加器值的限制。可以使用单元素阵列。数组变量没有改变 - 只是它指向的数组对象的内容正在改变,所以数组变量实际上是最终的,这是允许的:

public static IntUnaryOperator foo(int n) {
    return i -> n += i; // nope, sorry!
}

任何具有可变字段的对象都可以用作持有者。正如下面@andersschuller所建议的,AtomicInteger非常适合这里,并使返回的函数是线程安全的:

public static IntUnaryOperator foo(int n) {
    int[] value = new int[] { n };
    return i -> value[0] += i;
}

并且@srborlongan指出这可以使用方法引用重写,它甚至更短(虽然不是更易读):

public static IntUnaryOperator foo(int n) {
    AtomicInteger value = new AtomicInteger(n);
    return i -> value.addAndGet(i);
}