1

我有一个 Path 类,我认为它是不可变的。在另一个名为 Test 的类中,我有一个对 Path 对象的最终引用。

然而,在构造函数和 getter 方法之间,Path 对象发生了变化,即使它是不可变的并且引用是最终的。我知道这一点是因为 Path 中 int 数组节点的长度从构造函数变为 getter。看起来这个对象是一个完全不同的对象。

我的程序是多线程的,但我用单线程尝试过,但没有解决问题。

这是不可变的 Path 类

public class Path implements Iterable<Point> {

private final int[] nodes;
private final double distance;

    public Path(Scenario scenario, int gateway, int sensor){
        this.scenario = scenario;
        nodes = new int[2];

        nodes[1] = -gateway - 1;
        nodes[0] = sensor;

        distance = scenario.DISTANCE_GATEWAY_SENSOR[gateway][sensor];
    }

    public Path(Path base, int newSensor){
        scenario = base.scenario;

        //Copy the old path. These are rigid structures so that we do not need to deep copy
        nodes = new int[base.nodes.length + 1];
        for(int i = 0; i < base.nodes.length; i++)
                nodes[i + 1] = base.nodes[i];

        nodes[0] = newSensor;
        distance = base.distance + scenario.DISTANCE_SENSOR_SENSOR[newSensor][nodes[1]];
    }

    public Path(Scenario scenario, int[] nodes, boolean isSensor, double distance){
        this.scenario = scenario;
        this.distance = distance;
        this.nodes = Arrays.copyOf(nodes, nodes.length);

        if(!isSensor)
            for(int i = 0; i < this.nodes.length; i++)
                this.nodes[i] = -this.nodes[i] -1;
    }

    @Override
    public Iterator<Point> iterator() {
        return new PointIterator();
    }

    public class PointIterator implements Iterator<Point>{

        private int next = -1;

        @Override
        public boolean hasNext() {
            return next + 1 < nodes.length;
        }

        @Override
        public Point next() {
            int p = nodes[++next];
            if(p >= 0)
                return scenario.SENSOR_LOCATION[p];
            return scenario.CS_LOCATION[-p - 1];
        }

        @Override
        public void remove() {
            throw new IllegalAccessError("This method is not    supported");
        }

    }

}

这是 Test 类(最后引用 Path 类)

public class Test {

    private final Path gatewayTour;

    public Test(Scenario scenario, boolean[] chosenGateway){
        distanceFitness = 0;
        Point current = scenario.SINK_LOCATION;
        boolean visited[] = new boolean[scenario.CONFIG.NUM_CS];
        int nextGateway;

        LinkedList<Integer> order = new LinkedList<>();

        do {
            double minimumDistance = Double.MAX_VALUE;
            nextGateway = -1;
            for(int i = 0; i < scenario.CONFIG.NUM_CS; i++)
                if(!visited[i] && CHOSEN_GATEWAYS[i] && scenario.CS_LOCATION[i].isCloserThan(minimumDistance, current)) {
                    nextGateway = i;
                    minimumDistance = scenario.CS_LOCATION[i].distance(current);
                }

            if(nextGateway >= 0) {
                distanceFitness += minimumDistance;
                visited[nextGateway] = true;
                order.add(nextGateway);
                current = scenario.CS_LOCATION[nextGateway];
            }
        } while(nextGateway >= 0);

        int path[] = new int[order.size()];
        Iterator<Integer> it = order.iterator();
        for(int i = 0; i < order.size(); i++)
            path[i] = it.next().intValue();

        gatewayTour = new Path(scenario, path, false, distanceFitness);
    }

    public Path getGatewayTour(){
        //Here, the gatewayTour object has changed and does not have the same content as in the constructor
        return gatewayTour;
    }
 }

我的程序中有什么东西可以改变对象吗?我会更准确:有什么可以允许 Path 类中的 int 数组“节点”更改长度的吗?因为这是真正的问题。

[编辑]:我的测试有缺陷,这让我相信我的“节点”数组的值发生了变化。感谢所有指出我的代码中的缺陷或可能改进的人。

我会接受 AlexR 的回答,因为他指出可以更改最终数组中的单个元素;我不知道的东西,这有助于解决问题。

4

4 回答 4

7

Wordfinal表示用这个词标记的引用不能更改。这并不意味着引用的对象不能更改。

这意味着Path通过更改其字段来更改实例是没有问题的。是的,你是对的,你的领域也是最终的。但是让我们检查一下它们:

private final int[] nodes;
private final double distance;
private final Scenario scenario;

distance是一个原语,因此在初始化期间它确实不能更改。nodes是一个数组,即对象。数组本身不能更改,即引用指向同一个数组。但是,您可以更改数组的元素。

scenario也是对象。您还没有将类发送到Scenario这里,但是如果可以更改此类的字段,则可以更改此对象。

于 2013-06-09T17:16:32.603 回答
2
private final int[] nodes;

仍然是可变的,假设您的构造函数只是复制数组引用。

public Path(int[] nodes, double distance) {
    this.node = nodes;
    this.distance = distance;
}

这是因为Path'snodes仍然指向传入的实例。如果该实例更改,那么您Path的 's 状态已更改。

一种解决方案是node在构造函数中制作副本(使用System.arraycopy)。

于 2013-06-09T17:15:48.887 回答
0

为确保答案正确,我们需要查看更多代码;目前尚不清楚在哪里改变了什么。但是,如果这个想法是为了保证nodes是不可修改的,那么原始数组(最终或非最终数组)将不起作用。更像的东西

private final List<Integer> nodes;


public Path(Integer[] array /* note boxed as Integer */) {
     nodes = java.util.Collections.unmodifiableList(
       java.util.Arrays.asList(array));
     /* etc. */
}
于 2013-06-09T17:23:15.670 回答
0

这里有问题!

public Path(Scenario scenario, int[] nodes, boolean isSensor, double distance){
    this.scenario = scenario;
    this.distance = distance;
    this.nodes = nodes;

您复制节点数组引用

利用:

this.nodes = Arrays.copy(nodes, 0, nodes.length);

如果修改数组,更改将反映到Path! 同样,如果在构造函数中修改数组,更改将反映给调用者...

因此,您的课程目前不是一成不变的。此外,“真实”(在我的意义上)不可变类final本身就是。

于 2013-06-09T17:31:41.827 回答