如何使用Apache Commons-Lang3实现SudokuBoard类的equals方法?
问题描述
尝试使用Apache Commons-Lang3的EqualsBuilder实现SudokuBoard类的equals方法但未成功。需求是在所有数据模型类中高效重写toString()、equals()和hashCode()方法,要求equals与hashCode实现保持一致,当两个数独棋盘的数值相同时,equals方法应返回true。
SudokuBoard类代码
package org.sudoku; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import java.util.*; public class SudokuBoard { private final SudokuField[][] board = new SudokuField[9][9]; private final SudokuSolver solver; private final SudokuColumn[] columnObservers = new SudokuColumn[9]; private final SudokuRow[] rowObservers = new SudokuRow[9]; private final SudokuBox[] boxObservers = new SudokuBox[9]; private boolean state = true; public SudokuBoard(SudokuSolver solver) { this.solver = Objects.requireNonNull(solver); for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { board[i][j] = new SudokuField(); } } for (int y = 0; y < 9; y++) { List<SudokuField> fields = Arrays.asList(new SudokuField[9]); for (int i = 0; i < 9; i++) { fields.set(i, board[y][i]); } rowObservers[y] = new SudokuRow(fields); } for (int x = 0; x < 9; x++) { List<SudokuField> fields = Arrays.asList(new SudokuField[9]); for (int i = 0; i < 9; i++) { fields.set(i, board[i][x]); } columnObservers[x] = new SudokuColumn(fields); } int tableIndex = 0; for (int x = 0; x < 9; x += 3) { for (int y = 0; y < 9; y += 3) { List<SudokuField> fields = Arrays.asList(new SudokuField[9]); int fieldIndex = 0; for (int i = x; i < x + 3; i++) { for (int j = y; j < y + 3; j++) { fields.set(fieldIndex, board[i][j]); fieldIndex++; } } boxObservers[tableIndex] = new SudokuBox(fields); tableIndex++; } } int tableIndex2 = 0; for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { board[i][j].setObservers(columnObservers[i], rowObservers[j], boxObservers[tableIndex2]); if (j % 3 == 2) { tableIndex2++; } } tableIndex2 = i / 3; } } public void solveGame() { solver.solve(this); } public boolean isSafe() { return checkBoard(); } public int get(int y, int x) { return board[y][x].getFieldValue(); } public void set(int y, int x, int value) { try { board[y][x].setFieldValue(value); } catch (IllegalArgumentException e) { state = false; } } public SudokuRow getRow(int y) { return rowObservers[y]; } public SudokuColumn getColumn(int x) { return columnObservers[x]; } public SudokuBox getBox(int y, int x) { int row = y / 3 * 3; int col = x / 3 * 3; return boxObservers[col + 3 * row]; } private boolean checkBoard() { if (state) { return true; } state = true; for (int i = 0; i < 9; i++) { state &= columnObservers[i].verify() & rowObservers[i].verify() & boxObservers[i].verify(); } return state; } @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { stringBuilder.append(board[i][j].getFieldValue()); if (j < 8) { stringBuilder.append(" "); } } stringBuilder.append("\n"); } return stringBuilder.toString(); } }
原equals方法代码
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; SudokuBoard otherBoard = (SudokuBoard) obj; return new EqualsBuilder() .append(board, otherBoard.board) .append(solver, otherBoard.solver) .append(columnObservers, otherBoard.columnObservers) .append(rowObservers, otherBoard.rowObservers) .append(boxObservers, otherBoard.boxObservers) .append(state, otherBoard.state) .isEquals(); }
问题分析
- 错误比较无关字段:原方法加入了
solver、columnObservers等内部依赖和观察者实例的比较,这些字段与棋盘数值无关。两个数值完全相同的棋盘可能因使用不同的solver实例导致equals返回false,不符合需求。 - 数组比较方式错误:
EqualsBuilder.append()比较二维数组时默认对比引用而非内容,只有数组是同一对象时才会返回true,无法正确校验棋盘数值。 - SudokuField未重写equals/hashCode:如果SudokuField未正确实现这两个方法,即使数值相同,也会因引用不同导致比较失败。
解决方案
1. 为SudokuField重写equals和hashCode
SudokuField的相等逻辑仅由存储的数值决定:
public class SudokuField { private int fieldValue; // 原有方法(setFieldValue、getFieldValue、setObservers等)... @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SudokuField that = (SudokuField) o; return fieldValue == that.fieldValue; } @Override public int hashCode() { return Objects.hash(fieldValue); } }
2. 修正SudokuBoard的equals方法
仅比较棋盘数值(board数组内容)和state状态,使用EqualsBuilder.appendDeep()进行数组深度比较:
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; SudokuBoard other = (SudokuBoard) obj; return new EqualsBuilder() .appendDeep(board, other.board) // 深度比较二维数组内容 .append(state, other.state) .isEquals(); }
3. 实现一致的hashCode方法
基于equals中用到的字段生成哈希值,保证equals返回true的两个对象hashCode相同:
@Override public int hashCode() { return new HashCodeBuilder(17, 37) .appendDeep(board) .append(state) .toHashCode(); }
4. 高效重写toString方法(可选)
使用Commons-Lang的ToStringBuilder简化实现,可自定义输出格式:
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; // ... @Override public String toString() { // 需保持原有格式可保留原实现,或用ToStringBuilder生成结构化输出 return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) .append("board", board) .append("state", state) .toString(); }
关键说明
- equals与hashCode一致性:确保hashCode的计算依赖与equals中比较的字段完全一致,避免破坏HashMap等集合的行为。
- 数组比较:一维或多维数组必须使用深度比较(
Arrays.deepEquals或EqualsBuilder.appendDeep),而非直接对比引用。 - 无关字段排除:equals仅比较决定对象身份的核心字段,内部依赖、观察者等实例不应纳入比较逻辑。
内容的提问来源于stack exchange,提问作者jano
相关产品推荐
相关产品推荐

