累加器生成器测试 - Java 8

ban*_*rCZ 7 java paul-graham java-8

保罗格雷厄姆在他的伟大文章"复仇的书呆子"中声称,语言的力量各不相同.他提到了一个很好的练习 - 写一个累加器发生器:

我们想要编写一个生成累加器的函数 - 一个取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);
    }

}
Run Code Online (Sandbox Code Playgroud)

我很好奇,无论是在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
Run Code Online (Sandbox Code Playgroud)

Boa*_*ann 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
    }
}
Run Code Online (Sandbox Code Playgroud)

(而不是Function,我IntUnaryOperator在这里使用它,因为它允许使用原始ints而不是盒装Integers.它在逻辑上等同于Function<int,int>,如果这是合法的.)

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

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

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

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

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

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

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

public static IntUnaryOperator foo(int n) {
    return new AtomicInteger(n)::addAndGet;
}
Run Code Online (Sandbox Code Playgroud)

  • 我可能会建议用[`AtomicInteger`](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html)替换数组,或者甚至是在Java 8中添加了[`LongAdder`](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html). (7认同)
  • `foo(int)`也可以简单地定义为`return new AtomicInteger(n):: addAndGet;`,虽然我确信这样的代码可能不像原始代码那样可读(它做同样的事情:创建一个AtomicInteger来自给定整数的实例,然后封装实例的addAndGet方法(可能作为Function <Integer,Integer>或UnaryOperator <Integer>或IntUnaryOperator)). (3认同)
  • @StefanoSanfilippo因为你不能改变lambda中的n.变量必须(有效)最终.但是你可以在某个对象上创建一个最终变量点,例如一个包含非最终元素或字段的数组. (2认同)