
在使用 Uniform Cost Search 实现 AI 游戏(如叠方块)时,若直接将 this 添加到 ArrayList 中,所有元素实际指向同一内存地址,导致看似“添加了多个状态”,实则只保存了最终修改后的单一状态。根本原因在于 Java 的引用传递特性与对象浅拷贝误区。
在使用 uniform cost search 实现 ai 游戏(如叠方块)时,若直接将 `this` 添加到 `arraylist
Java 接口排错助手
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
下载
` 中,所有元素实际指向同一内存地址,导致看似“添加了多个状态”,实则只保存了最终修改后的单一状态。根本原因在于 java 的引用传递特性与对象浅拷贝误区。问题核心并非语法错误,而是对 Java 对象模型的典型误解:moves.add(this) 并非“保存当前棋盘快照”,而是将当前 Table 实例的引用存入列表。由于循环中反复调用 cube.setPosX() / cube.setPosY() 修改同一组 Cube 对象,且 this.setCubes(currentTable) 始终复用原始列表,所有 moves 元素最终都反映最后一次修改后的状态——这就是为何 moves.get(0).printTable() 与 moves.get(anyIndex).printTable() 输出完全一致。
要真正生成独立的状态副本,必须进行深拷贝(Deep Copy)。Table 类需提供显式的复制构造函数或 clone() 方法,确保每个新 Table 实例拥有自己独立的 ArrayList<cube></cube> 及其中每个 Cube 的副本:
public class Table implements Cloneable {
private ArrayList<Cube> cubes;
private int k;
// 复制构造函数:关键!创建完整深拷贝
public Table(Table other) {
this.k = other.k;
this.cubes = new ArrayList<>();
for (Cube cube : other.cubes) {
this.cubes.add(new Cube(cube.getId(), cube.getPosX(), cube.getPosY()));
}
}
// 或重写 clone()
@Override
public Table clone() {
try {
Table cloned = (Table) super.clone();
cloned.cubes = new ArrayList<>();
for (Cube cube : this.cubes) {
cloned.cubes.add(cube.clone()); // 假设 Cube 也实现了 clone()
}
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public ArrayList<Table> generateMoves() {
ArrayList<Table> moves = new ArrayList<>();
ArrayList<Cube> currentTable = this.getCubes();
int K = this.getK();
for (Cube cube : currentTable) {
int backupX = cube.getPosX();
int backupY = cube.getPosY();
for (int y = 1; y <= 3; y++) {
for (int x = 1; x <= 4 * K; x++) {
cube.setPosX(x);
cube.setPosY(y);
// 注意:isValid/isFree 应基于当前待验证状态,而非原表
if (cube.isValid(currentTable) && cube.isFree(currentTable)) {
// ✅ 创建全新 Table 实例,隔离状态
Table newState = new Table(this); // 深拷贝当前状态
// 更新新状态中的目标方块位置(需在 newState.cubes 中找到对应 Cube 修改)
Cube newCube = findCubeById(newState.getCubes(), cube.getId());
if (newCube != null) {
newCube.setPosX(x);
newCube.setPosY(y);
}
moves.add(newState);
}
// 恢复原 cube 位置(仅影响原始 this.cubes,不影响已添加的 newState)
cube.setPosX(backupX);
cube.setPosY(backupY);
}
}
}
return moves;
}
private Cube findCubeById(ArrayList<Cube> cubes, int id) {
return cubes.stream().filter(c -> c.getId() == id).findFirst().orElse(null);
}
}关键注意事项:
- ❌ 避免
moves.add(this):永远不要将 this 直接加入结果列表;
- ✅ 必须深拷贝:
Table 和 Cube 均需确保字段(尤其是可变集合与对象引用)被独立复制;
- ? 状态还原仅作用于原对象:
cube.setPosX(backupX) 是为了不影响后续循环,但已添加的 newState 不受影响;
- ?
isValid()/isFree() 的参数应传入待验证的目标状态(如 newState.getCubes()),而非原始 copyOfTable(该变量在此场景下冗余);
- ⚠️ 静态工具类(如答案中
TableUtil)虽能绕过问题,但破坏封装性、引入全局状态、无法支持并发,不推荐作为解决方案。
总结:AI 搜索算法依赖状态不可变性或明确拷贝语义。generateMoves() 的本质是“生成新状态”,而非“修改当前状态并记录”。坚持每次 add() 前创建完整深拷贝,是保证搜索树正确性的基石。
立即学习“Java免费学习笔记(深入)”;