2017-06-26 2 views
2

Ich habe eine Klasse, die Operationen auf einem Schachbrett macht. Jede Zelle hat ihre Zustände. Die Platte ist ein Array, deren Erklärung:Lambda als Methode mit Argumenten und Primitiven in Java

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

I viele Methoden, die 3 Argumente haben: Reihe (horizontal), Datei (vertikal) und Zustand. Sie überqueren Zelle für Zelle. Refactoring Code zu wiederholen, schrieb ich die folgende Methode:

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); 
} 

In einem der Verfahren, überprüfe ich, ob jede Zelle leer ist:

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

ich nicht die primitive boolean verwenden konnte, weil es unveränderlich. Zu diesem Zweck habe ich eine verschachtelte Klasse:

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; 
    } 
} 

Ich hoffe, es gibt einen besseren Weg, um ein Verfahren mit mehreren Argumenten in einem Lambda-Ausdruck übergeben. Ich bin mir bewusst, dass es möglich ist, Runnablelike in this answer zu verwenden. In diesem Fall kann ich jedoch keinen Parameter übergeben.

Ich hoffe, dass ich ExtendedBoolean nicht schreiben muss, deren einziger Zweck ist, ein primitiv zu wickeln. Ich musste auch ExtendedInteger schreiben.

Sind meine Hoffnungen vernünftig?

EDIT: Die Sammlung von Zellzuständen muss veränderbar sein. Ich ändere die Zustände der Zellen und überprüfe sie dann. Ich mache es in einer Schleife, bis die Bedingung erfüllt ist.

+1

Sie das Rad neu erfinden mit ExtendedBoolean und ExtendedInteger. Java verfügt bereits über Wrapper für Primitive, Boolean und Integer, um die von Ihnen verwendeten Namen zu benennen. – Dewick47

+0

Eh. Es gibt ein paar Ansätze für dieses Problem, aber ich würde keine von denen nennen, die lambdas nice verwenden. Lambdas sind nicht wirklich gut darin, den Staat zu erhalten. –

+0

Der Unterschied zwischen Ihrem 'ExtendedBoolean' und einem gewöhnlichen' Boolean' ist, dass es ** Mutable ** ist. Es gibt Mutables, die Sie stattdessen verwenden können (wie "AtomicBoolean"), aber die meisten haben unnötige Nebenwirkungen. – OldCurmudgeon

Antwort

2

könnten Sie einen Stream benutzen Sie Ihr Board zu vertreten und somit Nutzen aller Stream-Methoden:

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]); 
      }); 

, wo die Zellklasse als

definiert werden würde
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 + 
      '}'; 
    } 
} 

Nun zu implementieren Ihre Usecase, alles, was Sie tun müssten, wäre

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

Ich mag deinen Code sehr. Es vereinfacht sicherlich meine 'areAllEmpty' Methode. Das Problem ist, dass Ströme unveränderlich sind, aber ich muss die Zustände von Zellen viele Male ändern. –

1

Ein einfacher Weg ist Ihre eigene einfache generische Mutable schreiben:

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; 
    } 
} 

Sie dann ein Mutable<Boolean> in Ihrem Fall und setzt auf Autoboxing verwenden kann, um es boolean zu machen zu behandeln.

0

In Lambdas alle Variablen, die werden aus dem äußeren Bereich erfasst (clojure) müssen endgültig oder effektiv final sein, und deshalb lässt es Sie nicht direkt eine Variable ändern, wenn Javas Implementation für Clojures wie die von C# oder JavaScript wäre, müssten Sie dies nicht verwenden .

Ein in Java verwendetes Muster besteht darin, ein Array mit nur 1 Element in diesem Fall mit einem booleschen Array zu deklarieren und dann den Inhalt des Arrays zu ändern (ich denke, es ist hässlich).

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

Ich denke, diese Lösung (meiner Meinung nach) hässlich ist, ein anderer ist ein Halter-Klasse zu erstellen, das ist generisch, so, wenn Sie etwas Ähnliches müssen Sie es verwendet wird, könnte.

Als Beispiel:

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

Mit diesem letzten Ansatz sicher, dass es auch keine für primitive Typen erstellen, so dass es keine Boxen/Unboxing-Overhead.

BooleanHolder boolHolder = new BooleanHolder(false); 
+0

Ich denke auch, Ansatz mit einem Array ist zu hässlich. Ich sehe keine Verbesserung mit 'BooleanHolder' im Vergleich zu 'ExtendedBoolean', außer generischer Eigenschaft. @ OldCurmudgeon's Kommentar ist jedoch klarer für mich. –

+0

Ich denke, Sie sollten mit den generischen bleiben und für primitive Typen ihre eigene Version implementieren, auf diese Weise entfernen Sie die Kosten für Boxen und Unboxing, wenn Sie sich Java Functional Interfaces das taten sie für jeden primitiven Typ. –

1

können Sie versuchen, Generika und Lambda-Ausdrücke zusammen mit so etwas zu kombinieren:

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); 
} 

werfe ich auch in einem Beispiel, wie Sie Zählung tun können, um zu sehen, wie Zustand gehalten wird.

Aber ich mag @JB Nizet Antwort mit Streams.

+0

Der Nachteil ist, dass ich zurückkehren muss, auch wenn ich die Zellzustände ändere. Ich müsste 2 Methoden schreiben: 'traverseBoardChangingStates' und' traverseBoardCheckingStates'. –

+0

Geben Sie einfach 'null' zurück, wenn Sie den Wert nicht benötigen. – tsolakp

0

Ihre Methode traverseBoard ist korrekt, wenn Sie nur eine Aktion für jede Zelle ausführen möchten. Wenn Sie jedoch eine Bedingung für jede Zelle überprüfen müssen, benötigen Sie einen anderen Ansatz.

Wie ich sehe, ist das Problem, dass Sie eine Methode entwerfen müssen, die Ihre Platine durchquert und boolean anstelle von void zurückgibt. Hier ist etwas zu beginnen:

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); 
} 

Dieser Ansatz, neben wie erwartet ohne einen Wrapper Typ zu benötigen, ist effizienter, weil es so bald aufhört Iterieren als die Bedingung nicht für eine Zelle erfüllt ist, während der Ansatz, den Sie Sie benutzten das ganze Brett.

Ihr Beispiel, das alle Zellen überprüft, ob leer sind nun wie folgt neu geschrieben werden könnte:

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