Lambda 作为 Java 中带有参数和原语的方法

Lambda as method with arguments and primitives in Java

我有一个 class 在棋盘上进行操作。每个细胞都有它的状态。该板是一个数组,其声明为:

private CellState[][] cellBoard = new CellState[8][8];

我有很多方法有 3 个参数:行(水平)、文件(垂直)和状态。他们逐个单元地遍历电路板。为了重构重复代码,我写了下面的方法:

private void traverseBoard(Command method) {
    for(int row = 0; row < 8; row++) {
        for(int file = 0; file < 8; file++) {
            method.execute(row, file, cellBoard[row][file]);
        }
    }
}

protected interface Command {
    public void execute(int x, int y, CELL_STATE state);
}

在其中一种方法中,我检查每个单元格是否为空:

private boolean areAllEmpty() {
    ExtendedBoolean empty = new ExtendedBoolean(true);
    traverseBoard((i, j, state) -> {
        if (state != CELL_STATE.EMPTY) {
            empty.set(false);
        }
    });
    return empty.is();
}

我无法使用原语 boolean,因为它是不可变的。为此,我创建了一个嵌套的 class:

private class ExtendedBoolean {
    boolean bool;

    public ExtendedBoolean(boolean bool) {
        this.bool = bool;
    }

    public void set(boolean bool) {
        this.bool = bool;
    }

    public boolean is() {
        return bool;
    }
}

我希望有更好的方法在 lambda 表达式中传递具有多个参数的方法。我知道可以使用 Runnable like in this answer。但是,在那种情况下我不能传递任何参数。

我希望我不必写ExtendedBoolean,它的唯一目的是包装一个原语。我也必须写ExtendedInteger

我的希望合理吗?

编辑: 细胞状态的集合必须是可变的。我改变细胞的状态然后检查它们。我循环执行直到满足条件。

一种简单的方法是编写您自己的简单通用 Mutable:

public class Mutable<T> {
    T it;

    public Mutable(T it) {
        this.it = it;
    }

    public void set(T it) {
        this.it = it;
    }

    public T get() {
        return it;
    }
}

然后您可以在您的案例中使用 Mutable<Boolean> 并依靠自动装箱使其处理 boolean

您可以使用 Stream 来表示您的看板,从而受益于所有 Stream 方法:

    Stream<Cell> stream =
        IntStream.range(0, 64)
            .mapToObj(i -> {
                int row = i / 8;
                int column = i % 8;
                return new Cell(row, column, cellBoard[row][column]);
            });

单元格 class 将被定义为

private static class Cell {
    private final int row;
    private final int column;
    private final CELL_STATE state;

    public Cell(int row, int column, CELL_STATE state) {
        this.row = row;
        this.column = column;
        this.state = state;
    }

    public int getRow() {
        return row;
    }

    public int getColumn() {
        return column;
    }

    public CELL_STATE getState() {
        return state;
    }

    @Override
    public String toString() {
        return "Cell{" +
            "row=" + row +
            ", column=" + column +
            ", state=" + state +
            '}';
    }
}

现在,要实现您的用例,您所要做的就是

stream.allMatch(cell -> cell.getState() == CELL_STATE.EMPTY);

在 lambdas 中,从外部范围 (clojure) 捕获的所有变量都需要是最终的,或者实际上是最终的,这就是为什么它不允许您直接更改变量,如果 Javas 实现clojures 就像那些 C# 或 JavaScript 你不需要使用它。

Java中使用的一种模式是声明一个只有 1 个元素的数组,在这种情况下,数组中有一个布尔值,然后更改数组的内容(我认为这很丑)。

boolean[] empty = new boolean[]{false};
traverseBoard((i,j,state) -> empty[0] = true ) // Simple usage

我认为这个解决方案(我的意见)很丑陋,另一个是创建一个 Holder class,这是通用的,所以只要你需要类似的东西,你就可以使用它。

举个例子:

Holder<Student> studentHolder = new Holder<>(student); doSomething( anotherStudent -> studentHolder.value = anotherStudent );

使用最后一种方法一定要为原始类型创建默认类型,这样就没有 Boxing/Unboxing 开销。

BooleanHolder boolHolder = new BooleanHolder(false);

您可以尝试将泛型和 lambda 结合在一起,像这样:

protected interface Command<T> {
    public T execute(T prev, int x, int y, CELL_STATE state);
}


private <T> T traverseBoard(T initial, Command<T> method) {
    T result = initial;
    for(int row = 0; row < 8; row++) {
        for(int file = 0; file < 8; file++) {
            result = method.execute( result, row, file, null );
        }
    }
    return result;
}

private boolean areAllEmpty() {
    return traverseBoard( Boolean.FALSE, (b, i, j, state) -> {
        if ( state != CELL_STATE.EMPTY ) {
            return false;
        }else{
            return true;
        }
    } );
}

private int count() {
    return traverseBoard( 0, (c, i, j, state) -> c += 1 );
}

我还提供了一个示例,说明如何进行计数,以了解如何维护状态。

但我喜欢@JB Nizet 使用流回答。

如果您只想对每个单元格执行一些操作,那么您的 traverseBoard 方法是正确的。但是,如果您需要检查每个单元格的某些条件,则需要另一种方法。

如我所见,问题是您需要设计一种方法来遍历您的电路板和 returns 一个 boolean 而不是 void。以下是开始的内容:

private boolean allCellsInBoardMatch(Condition condition) {
    for (int row = 0; row < 8; row++) {
        for (int file = 0; file < 8; file++) {
            if (!condition.check(row, file, cellBoard[row][file])) {
                return false;
            }
        }
    }
    return true;
}

protected interface Condition {
    boolean check(int x, int y, CELL_STATE state);
}

这种方法除了可以按预期工作而不需要包装类型外,效率更高,因为它会在单元格不满足条件时立即停止迭代,而您使用的方法是遍历整个板。

检查所有单元格是否为空的示例现在可以重写如下:

private boolean areAllEmpty() {
    return allCellsInBoardMatch((i, j, state) -> state == CELL_STATE.EMPTY);
}