1

不変だと思うPathクラスがあります。Test という別のクラスには、Path のオブジェクトへの最終的な参照があります。

ただし、コンストラクターと getter メソッドの間で、Path オブジェクトは不変で参照が最終的であっても変更されます。これは、Path の int 配列ノードの長さがコンストラクターからゲッターに変わるためです。その物体はまったく別のもののようです。

私のプログラムはマルチスレッドですが、シングル スレッドで試してみましたが、問題は解決しませんでした。

ここに不変の 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 配列「ノード」の長さを変更できるものはありますか? これが本当の問題だからです。

[編集]: 私のテストに欠陥があったため、'nodes' 配列の値が変更されたと思い込んでしまいました。私のコードの欠陥や改善の可能性を指摘してくれたすべての人に感謝します。

最終的な配列の個々の要素を変更できると彼が指摘したので、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の状態が変更されたことになります。nodesPath

node1 つの解決策は、コンストラクターで ( を使用して)のコピーを作成することSystem.arraycopyです。

于 2013-06-09T17:15:48.887 に答える
0

答えが正しいことを確認するには、さらにコードを確認する必要があります。何がどこで変更されているかは明らかではありません。ただし、それが変更不可能であることを保証することが目的である場合nodes、プリミティブ配列 (final であろうとなかろうと) は機能しません。もっと似たもの

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 に答える