2013-06-09 2 views
1

저는 변경할 수없는 Path 클래스가 있습니다. Test라는 또 다른 클래스에서는 Path 객체에 대한 마지막 참조가 있습니다.Java에서의 최종 가변 변수 변경

그러나 생성자와 getter 메서드 사이에 Path 객체는 변경 가능하지 않고 참조가 final 임에도 불구하고 변경됩니다. Path의 int 배열 노드 길이가 생성자에서 getter로 변경되기 때문에이 사실을 알고 있습니다. 그것은 대상이 완전하게 다른 것 인 것처럼 보인다.

내 프로그램은 멀티 스레드이지만 단일 스레드로 시도했지만 문제가 해결되지 않았습니다. 여기

는 불변의 경로 클래스

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"); 
     } 

    } 

} 

이며, 여기에 테스트 클래스는 객체를 허용

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; 
    } 
} 

내 프로그램 있나요 (경로 클래스에 대한 최종 참조)입니다 바꾸다? 나는 더 정확해질 것이다 : woud가 Path 클래스의 int 배열 "노드"가 길이를 바꿀 수 있도록 허용 할 것인가? 이것이 실제 문제이기 때문입니다.

[편집] : 내 테스트에 결함이있어 내 노드 배열의 가치가 바뀌 었다고 믿었습니다. 내 코드의 결함이나 가능한 개선 사항을 지적한 모든 사람들에게 감사드립니다.

AlexR의 대답은 최종 배열에서 개별 요소를 변경할 수 있다고 지적했기 때문에 받아 들일 것입니다. 내가 모르는 어떤 것이 문제를 해결하는 데 도움이됩니다.

+1

'// 변수를 초기화하기 위해 여기에 일부 생성자를 표시 하시겠습니까?' – fge

+3

BTW,'.remove()'는'UnsupportedOperationException'을 throw해야합니다. – fge

+1

최종 컨테이너의 내용을 변경할 수는 있지만 참조 해제하거나 다시 참조 할 수는 없습니다. 생성자와 getter를 볼 수 있습니까? – 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; 
} 

Pathnodes 여전히 전달 된 인스턴스를 가리키고 있습니다. 인스턴스가 Path 다음, 변경하는 경우 '때문이다의 상태가 변경되었습니다.

하나의 해결책은 생성자에 node 사본을 만드는 것입니다 (System.arraycopy 사용).

0

답변이 정확한지 확인하려면 더 많은 코드가 필요합니다. 무엇이 어디에서 변경되고 있는지 명확하지 않습니다.그러나, 에 대한 보장이 인 경우 nodes은 수정할 수 없으므로 프리미티브 배열 (final 또는 not)은 작동하지 않습니다. 더 좋아하는 것

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, 가능합니다. 나는 그가 코드에서 어떻게 사용했는지 보지 않고, 완벽하게 ~ 불가능한 오브젝트를 원했던 설명에서부터 일하고있었습니다. –

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입니다.