简体   繁体   English

Java中的最终变量和不可变变量

[英]Final and Immutable Variable Changes in Java

I have a Path class which I think is immutable. 我有一个Path类,我认为它是不可变的。 In another class called Test, I have a final reference to an object of Path. 在另一个称为Test的类中,我最终引用了Path对象。

Yet, in between the constructor and the getter method, the Path object changes even though it is immutable and the reference is final. 但是,在构造函数和getter方法之间,即使Path对象是不可变的,并且引用是最终的,Path对象也会更改。 I know this because the length of the int array node in Path changes from the constructor to the getter. 我知道这是因为Path中int数组节点的长度从构造函数更改为getter。 It seems as it the object is a completly different one altogether. 看起来对象完全是完全不同的对象。

My program is multi-threaded but I've tried it with a single thread and that didn't resolve the problem. 我的程序是多线程的,但是我已经用一个线程尝试过了,但是并不能解决问题。

Here is the immutable Path class 这是不可变的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");
        }

    }

}

and here is the Test class (with a final reference to the Path class) 这是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;
    }
 }

Is there anything in my program that permits the object to change? 我的程序中是否有任何允许对象更改的内容? I'll be more precise: is there anything that woud permit the int array "nodes" in Path class to change length? 我会更加精确:是否有什么可以允许Path类中的int数组“节点”更改长度? Because this is the real problem. 因为这是真正的问题。

[EDIT]: My test was flawed, which led me to believe that the value of my 'nodes' array changed. [编辑]:我的测试有缺陷,这使我相信“​​节点”数组的值已更改。 Thanks to all who pointed flaws or possible improvements in my code. 感谢所有指出缺陷或可能对我的代码进行改进的人。

I'll accept the answer of AlexR because he pointed out that one could change individual elements in a final array; 我会接受AlexR的回答,因为他指出可以更改最终数组中的单个元素; something I didn't know and that help resolve the problem. 我不知道的东西,可以帮助解决问题。

Word final means that the reference marked with this word cannot be changed. 单词final表示标记有该单词的参考不能更改。 It does not mean that the referenced object cannot be changed. 这并不意味着引用的对象不能更改。

This means that there is no problem to change the instance of Path by changing its fields. 这意味着通过更改其字段来更改Path的实例没有问题。 Yes, you are right, your fields are final too. 是的,你是对的,你的领域也是最终的。 But let's examine them: 但是让我们检查一下它们:

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

distance is a primitive, so it indeed cannot be changed once assigned during initialization. distance是一个原始变量,因此在初始化期间分配后确实无法更改。 nodes is an array, ie object. nodes是一个数组,即对象。 The array itself cannot be changed, ie the reference refers to the same array. 数组本身不能更改,即,引用引用同一数组。 However you can change elements of the array. 但是,您可以更改数组的元素。

scenario is object too. scenario也是对象。 You have not sent the class Scenario here, but again if fields of this class can be changed this object can be changed. 您尚未在此处发送Scenario ,但是如果可以更改此类的字段,则可以再次更改该对象。

private final int[] nodes;

Is still mutable, assuming your constructor simply copies the array reference. 仍然是可变的,假设您的构造函数只是复制数组引用。

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

This is because Path 's nodes is still pointing to the instance that was passed in. If that instance changes, then your Path 's state has changed. 这是因为Pathnodes仍指向传入的实例。如果该实例发生更改,则您Path的状态也已更改。

One solution is to make a copy of node in the constructor (using System.arraycopy ). 一种解决方案是在构造函数中复制node (使用System.arraycopy )。

To be sure the answer is correct we need to see more code; 为确保答案正确,我们需要查看更多代码; it's not clear what is being changed where. 目前尚不清楚在哪里更改了什么。 However, if the idea is to guarantee that nodes is unmodifiable, a primitive array (final or not) will not work. 但是,如果要保证 nodes不可修改,则原始数组(最终数组或非最终数组)将不起作用。 Something more like 更像

private final List<Integer> nodes;


public Path(Integer[] array /* note boxed as Integer */) {
     nodes = java.util.Collections.unmodifiableList(
       java.util.Arrays.asList(array));
     /* etc. */
}

Problem here! 问题在这里!

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

You copy the nodes array reference . 您复制节点数组引用

Use: 采用:

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

If you modify the array, changes will be reflected into Path ! 如果修改数组,更改将反映在Path Similarly, if you modify the array in the constructor, changes will be reflected to the caller... 同样,如果您在构造函数中修改数组,则更改将反映给调用方...

As such, your class is NOT immutable at the moment. 因此,您的课程目前不是一成不变的。 Also, "real" (to my sense) immutable classes are final themselves. 同样,“真实的”(就我而言)不可变的类本身也是final

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM