lambda 表达式是否具有无法通过其他东西复制的独特用途?

Do lambda expressions have a unique use that cannot be replicated through something else?

我今天在学习 lambda 表达式,到目前为止,我还没有发现它们的独特用途。所以我问自己,它们是否不仅仅是一个取自函数式语言的方便的小工具。

什么是 Lambda 表达式?

根据我的阅读,它们可用于实例化匿名 类、传递简单方法和过滤集合中的元素。 例如:

List<Integer> list = List.of(1, 2, 3, 4, 5);
   for (int i : list)
      System.out.println(i);

...可以写成:

List<Integer> list = List.of(1, 2, 3, 4, 5);
list.forEach((i) -> {
   System.out.println(i);
});

但是你为什么要那样做?每次你写下一个 lambda 表达式时,你可以只把原始表达式放在那里!

public class Test {
    @FunctionalInterface
    interface BoolOperation {
        boolean execute(boolean param1, boolean param2);
    }

    static void test(BoolOperation op, boolean param1, boolean param2) {
        System.out.println(op.execute(param1, param2));
    }

    public static void main(String[] args) {
        test((x, y) -> x && y, true, false);
        
        test((x, y) -> x || y, false, true);
        
        test((x, y) -> x == y, true, true);
    }
}

...与以下内容相同:

public class Test {
    public static void main(String[] args) {
        System.out.println(true && false);
        System.out.println(false || true);
        System.out.println(true == true);
    }
}

public class Test {
    public static void main(String[] args) {
        Runnable r = () -> {
                System.out.println("Running.");
        };
    }   
}

... shorter/readable 并不比

public class Test {
    public static void main(String[] args) {
        Runnable r = new ThreadThingy();
    }   
}

class ThreadThingy implements Runnable {
    public void run() {
        System.out.println("Running.");
    }
}

我读过 Oracle-Docs,但它们非常具体,没有突出一般用途。

当然,你的布尔表达式的例子直接写起来更容易。但是如果你想为不同的布尔表达式创建真值表呢?

public class Test {
    @FunctionalInterface
    interface BoolOperation {
        boolean execute(boolean param1, boolean param2);
    }

    static void line(String s, BoolOperation op, boolean b1, boolean b2) {
        System.out.printf("%b %s %b = %b%n", b1, s, b2, op.execute(b1, b2));
    }

    static void table(String s, BoolOperation op) {
        System.out.println("Truth table for "+s);
        line(s, op, false, false);
        line(s, op, false, true);
        line(s, op, true, false);
        line(s, op, true, true);
        System.out.println();
    }

    public static void main(String[] args) {
        table("AND", (x, y) -> x && y);
        table("OR", (x, y) -> x || y);
        table("XNOR", (x, y) -> x == y);
        table("XOR", (x, y) -> x != y);
        table("implies", (x, y) -> !x || y);
    }
}

对于各种操作,您仍然可以使用匿名 classes 来做到这一点,但是这将需要更多的“样板” - 需要编写的代码只是为了满足编译器而不增加任何价值。

例如,您可以这样编写第一个 table 调用:

    table("AND", new BoolOperation() {
        @Override
        public boolean execute(boolean param1, boolean param2) {
            return param1 && param2;
        }
    });

但你不能告诉我这比

更具可读性和更容易理解
        table("AND", (x, y) -> x && y);

对于一个更“真实世界”的例子,我的一个项目包含一个 class ObjectCsvWriter,它需要一个 PrintWriter 和一个 Column 定义列表来导出对象列表到 csv 文件中。

列定义包含列名和用于从该行的对象中提取该列值的 Function<T, X>X 取决于列类型 - 对于 StringColumn 预期函数是 Function<T, String>,对于 DoubleColumn 预期函数是 Function<T, Double>.

导出代码类似于

private void exportXxx(List<ExportableXxx> data) {
    List<Column<ExportableXxx>> columns = Arrays.asList(
        new StringColumn<>("Xxx Id", ExportableXxx::getId),
        new StringColumn<>("Xxx Name", ExportableXxx::getName),
        new DoubleColumn<>("Total YYYY", "%.2f", ExportableXxx::getTotal),
        new DoubleColumn<>("Yyyy Type 1", "%.2f", e -> e.getYyyy(Types.Type1)),
        new DoubleColumn<>("Yyyy Type 2", "%.2f", e -> e.getYyyy(Types.Type2)),
        new DoubleColumn<>("Yyyy Type 3", "%.2f", e -> e.getYyyy(Types.Type3)),
        new DoubleColumn<>("Yyyy Type 4", "%.2f", e -> e.getYyyy(Types.Type4)),
        new DoubleColumn<>("Yyyy Other", "%.2f", ExportableXxx::getYyyOther)
    );
    try (ObjectCsvWriter<ExportableXxx> cw = new ObjectCsvWriter<>(
            new PrintWriter(fileName, Charset.defaultCharset()), columns)) {
        cw.printHeader();
        for (ExportableXxx d: data) {
            cw.printLine(d);
        }
    } catch (IOException e) {
        logger.warn("Cannot write export file", e);
    }
}

您能想象为列出的每个列创建匿名 class 需要付出的努力吗? (此示例不仅使用 lambda 来提取值,因为有时使用方法引用甚至更容易)。