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);
}
我有一个 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);
}