2013-06-09 4 views
1

У меня есть класс Path, который, как я думаю, неизменен. В другом классе, называемом Test, у меня есть окончательная ссылка на объект Path.Конечные и неизменные переменные изменения в Java

Тем не менее, между конструктором и методом геттера объект 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; 
    } 
} 

Есть ли что-нибудь в моей программе, которая позволяет объект изменить? Я уточню: есть ли что-нибудь, что позволило бы «узлам» массива int в классе Path изменить длину? Потому что это настоящая проблема.

[EDIT]: Мое испытание было ошибочным, что заставило меня поверить, что значение моего массива «узлов» изменилось. Спасибо всем, кто указал на недостатки или возможные улучшения в моем коде.

Я согласен с ответом AlexR, потому что он указал, что можно изменить отдельные элементы в конечном массиве; то, чего я не знал, и что помогает решить проблему.

+1

Можете ли вы показать свои '// некоторые конструкторы здесь для инициализации переменных? – fge

+3

BTW, '.remove()' should throw 'UnsupportedOperationException' – fge

+1

Вы можете изменить содержимое конечных контейнеров, но не отменять ссылку или повторно ссылаться на него, можем ли мы увидеть конструкторы и геттеры? – arynaq

ответ

7

Слово final означает, что ссылка, помеченная этим словом, не может быть изменена. Это не означает, что ссылочный объект не может быть изменен.

Это означает, что нет необходимости изменять экземпляр Path, изменив его поля. Да, вы правы, ваши поля тоже окончательные. Но давайте рассмотрим их:

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

distance является примитивным, так что на самом деле не может быть изменена после во время инициализации. nodes представляет собой массив, то есть объект. Сам массив не может быть изменен, т. Е. Ссылка ссылается на тот же массив. Однако вы можете изменять элементы массива.

scenario - объект тоже. Вы не отправили класс Scenario здесь, но опять же, если поля этого класса могут быть изменены, этот объект можно изменить.

+0

Сценарий в данном случае не имеет значения. Я не использую его для вычисления чего-либо. Я удаляю его из примера кода – Bathlamos

+0

Переменная узлов изменяет длину. Это странно. – Bathlamos

+0

Конечно, в «Сценарии» есть массивы, которые изменяются. –

2
private final int[] nodes; 

По-прежнему изменен, если ваш конструктор просто копирует ссылку на массив.

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

Это потому, что Path «s nodes все еще указывает на экземпляр, который был принят. Если этот экземпляр изменится, то ваши Path» состояние S изменилось.

Одним из решений является копия node в конструкторе (с использованием System.arraycopy).

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. */ 
} 
+0

OP хочет сохранить массив, однако ... 'Array.copy()' будет прекрасно. Кроме того, OP никогда не возвращает массив напрямую AFAICS – fge

+0

@fge, очень возможно. Я работал над описанием, где он, казалось, хотел полностью _immutable_ object, вместо того, чтобы видеть, как он использовал его в коде. –

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.

Смежные вопросы