You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

递归中list.add(object)后对象突变原因及迷宫寻路实现问询

递归中List添加对象后被意外修改的原因及解决方案

这个问题我太熟了——本质上是Java引用传递的坑,刚好你做的迷宫路径场景是最容易踩这个坑的典型案例之一!

问题根源:List存的是对象引用,不是副本

Java里所有对象都是引用类型,当你调用list.add(object)时,List里存储的并不是这个对象的“快照”,而是指向对象内存地址的引用。如果在递归过程中,你复用了同一个对象实例(比如反复修改它的坐标、状态),那List里所有之前添加的元素,都会因为指向同一个内存地址,跟着后续的修改一起变化。

放到你的迷宫场景里举个例子:假设你在go()方法里用了同一个Square对象,每次递归时修改它的x/y坐标,然后把它加入当前路径的List。那最后你打印路径时会发现,所有路径里的Square都变成了递归最后一步的那个对象——因为它们全是同一个引用!

针对迷宫路径场景的两种解决方案

方案1:回溯时维护路径(更高效,推荐)

如果你的目标是收集所有出口路径,最常用的做法是回溯法:每次进入递归前把当前节点加入路径,递归返回后(不管有没有找到出口)再把这个节点从路径里移除。这样路径List始终只维护当前递归分支的状态,不会互相干扰。

唯一需要注意的是:当找到出口时,要保存路径的副本,而不是原路径的引用。因为后续回溯会修改原路径,直接存引用的话,保存下来的路径会跟着变。

示例代码片段:

// 假设这是你的递归方法
private void go(Square current, List<Square> currentPath, List<List<Square>> allExitPaths) {
    // 标记当前节点已访问(避免重复走)
    current.setVisited(true);
    currentPath.add(current);

    // 找到出口,保存路径副本
    if (current instanceof Opening) {
        allExitPaths.add(new ArrayList<>(currentPath));
    } else {
        // 遍历相邻方块
        for (Square neighbor : getNeighbors(current)) {
            if (neighbor instanceof WhiteSquare && !neighbor.isVisited()) {
                go(neighbor, currentPath, allExitPaths);
            }
        }
    }

    // 回溯:移除当前节点,取消访问标记
    currentPath.remove(currentPath.size() - 1);
    current.setVisited(false);
}

这里new ArrayList<>(currentPath)就是创建了当前路径的副本,把它加入allExitPaths后,后续回溯修改原currentPath不会影响已经保存的路径。

方案2:每次添加时创建对象副本

如果你不想用回溯(比如不需要回溯状态),那每次往List里添加对象时,都要创建一个当前对象的深拷贝(或者浅拷贝,根据你的Square类属性决定)。

比如你的Square类有x、y坐标,那可以给Square加一个拷贝构造方法:

public class Square {
    private int x;
    private int y;
    private boolean visited;
    // 其他属性...

    // 拷贝构造方法
    public Square(Square other) {
        this.x = other.x;
        this.y = other.y;
        this.visited = other.visited;
        // 复制其他必要属性
    }
}

// 子类比如WhiteSquare也需要实现拷贝构造
public class WhiteSquare extends Square {
    public WhiteSquare(WhiteSquare other) {
        super(other);
        // 复制子类特有的属性
    }
}

然后在递归里添加时:

Square copy = new WhiteSquare((WhiteSquare) current);
currentPath.add(copy);

这种方式的缺点是每次都要创建新对象,性能不如回溯法,而且如果Square类有复杂的引用类型属性,还需要做深拷贝,否则还是会有引用共享的问题。

总结

核心就是记住:List存储的是对象引用,不是对象本身。在递归这种需要维护多个状态分支的场景里,要么用回溯法临时维护路径并在保存时做副本,要么每次添加时创建对象的独立副本,这样就能避免“对象被意外修改”的问题了。

内容的提问来源于stack exchange,提问作者Snusifer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:34:57