내 ArrayList에 목록에 마지막으로 추가 된 항목의 N 개의 복사본이 포함 된 이유는 무엇입니까?


84

ArrayList에 세 개의 다른 개체를 추가하고 있지만 목록에는 마지막으로 추가 한 개체의 복사본 세 개가 포함되어 있습니다.

예를 들면 :

for (Foo f : list) {
  System.out.println(f.getValue());
}    

예상 :

0
1
2

실제 :

2
2
2

내가 어떤 실수를 했습니까?

참고 : 이것은이 사이트에서 발생하는 수많은 유사한 문제에 대한 표준 Q & A로 설계되었습니다.

답변:


152

이 문제에는 두 가지 일반적인 원인이 있습니다.

  • 목록에 저장 한 개체에서 사용하는 정적 필드

  • 실수로 동일한 개체를 목록에 추가

정적 필드

목록의 개체가 정적 필드에 데이터를 저장하는 경우 목록의 각 개체는 동일한 값을 보유하므로 동일하게 나타납니다. 아래 클래스를 고려하십시오.

public class Foo {
  private static int value; 
  //      ^^^^^^------------ - Here's the problem!
  
  public Foo(int value) {
    this.value = value;
  }
  
  public int getValue() {
    return value;
  }
}

이 예제에서는 선언 int value되어 Foo있기 때문에의 모든 인스턴스간에 공유되는 하나만 static있습니다. ( "클래스 멤버 이해" 참조 자습서를 .)

Foo아래 코드를 사용하여 목록 에 여러 개체를 추가하면 각 인스턴스가에 3대한 호출에서 반환 됩니다 getValue().

for (int i = 0; i < 4; i++) {      
  list.add(new Foo(i));
}

해결책은 간단 static합니다. 실제로 해당 클래스의 모든 인스턴스간에 값을 공유하려는 경우가 아니면 클래스의 필드에 키워드를 사용하지 마십시오 .

동일한 개체 추가

목록에 임시 변수를 추가하는 경우 반복 할 때마다 추가중인 객체의 새 인스턴스를 만들어야합니다. 다음 오류 코드 스 니펫을 고려하십시오.

List<Foo> list = new ArrayList<Foo>();    
Foo tmp = new Foo();

for (int i = 0; i < 3; i++) {
  tmp.setValue(i);
  list.add(tmp);
}

여기서 tmp객체는 루프 외부에서 생성되었습니다. 결과적으로 동일한 개체 인스턴스 가 목록에 세 번 추가됩니다. 인스턴스는 2에 대한 마지막 호출 중에 전달 된 값이기 때문에 값을 보유합니다 setValue().

이 문제를 해결하려면 개체 구성을 루프 내부로 이동하면됩니다.

List<Foo> list = new ArrayList<Foo>();        

for (int i = 0; i < 3; i++) {
  Foo tmp = new Foo(); // <-- fresh instance!
  tmp.setValue(i);
  list.add(tmp);
}

1
안녕하세요 @ Duncan 좋은 솔루션입니다. "Adding the Same Object"에서 세 개의 다른 인스턴스가 값 2를 보유하는 이유를 묻고 싶습니다. 세 인스턴스가 모두 3 개의 다른 값을 보유해야하지 않습니까? 곧 답장을
Dev

3
@Dev 같은 개체 ( tmp)가 목록에 세 번 추가되기 때문입니다. 그리고이 객체는 tmp.setValue(2)루프의 마지막 반복에서를 호출하기 때문에 2의 값을 갖습니다 .
Duncan Jones

1
좋아 그래서 여기 문제는 동일한 객체 때문입니다. 그래서 배열 목록에 동일한 객체를 세 번 추가하면 arraylist obj의 세 위치가 모두 동일한 객체를 참조합니까?
Dev

1
@Dev Yup, 그게 바로 그것입니다.
Duncan Jones

1
내가 처음에 간과 한 명백한 사실 : you must create a new instance each time you loop섹션 의 부분 에 관하여 Adding the same object: 참조 된 인스턴스는 추가하는 객체가 아니라 추가하는 객체와 관련이 있습니다.
에서

8

문제는 static루프가 반복 될 때마다 새로운 초기화가 필요한 유형에 있습니다. 루프에있는 경우 구체적인 초기화를 루프 내부에 유지하는 것이 좋습니다.

List<Object> objects = new ArrayList<>(); 

for (int i = 0; i < length_you_want; i++) {
    SomeStaticClass myStaticObject = new SomeStaticClass();
    myStaticObject.tag = i;
    // Do stuff with myStaticObject
    objects.add(myStaticClass);
}

대신에:

List<Object> objects = new ArrayList<>(); 

SomeStaticClass myStaticObject = new SomeStaticClass();
for (int i = 0; i < length; i++) {
    myStaticObject.tag = i;
    // Do stuff with myStaticObject
    objects.add(myStaticClass);
    // This will duplicate the last item "length" times
}

다음 tagSomeStaticClass위 코드 조각의 유효성을 확인 하는 변수입니다 . 사용 사례에 따라 다른 구현을 가질 수 있습니다.


"유형 static" 이란 무엇을 의미 합니까? 당신에게 비 정적 클래스는 무엇입니까?
4castle

예 : 비 정적 : public class SomeClass{/*some code*/} & 정적 : public static class SomeStaticClass{/*some code*/}. 이제 더 명확 해 졌으면합니다.
Shashank

1
정적 클래스의 모든 객체는 루프에서 초기화되고 각 반복에서 다른 값으로 설정되면 동일한 주소를 공유합니다. 그들 모두는 마지막 반복의 값이나 마지막 객체가 수정되었을 때와 같은 동일한 값을 갖게 될 것입니다. 이제 더 명확 해 졌으면합니다.
Shashank

6

캘린더 인스턴스에서 동일한 문제가 발생했습니다.

잘못된 코드:

Calendar myCalendar = Calendar.getInstance();

for (int days = 0; days < daysPerWeek; days++) {
    myCalendar.add(Calendar.DAY_OF_YEAR, 1);

    // In the next line lies the error
    Calendar newCal = myCalendar;
    calendarList.add(newCal);
}

달력의 새 개체를 만들어야합니다 calendar.clone().

Calendar myCalendar = Calendar.getInstance();

for (int days = 0; days < daysPerWeek; days++) {
    myCalendar.add(Calendar.DAY_OF_YEAR, 1);

    // RIGHT WAY
    Calendar newCal = (Calendar) myCalendar.clone();
    calendarList.add(newCal);

}

4

ArrayList에 개체를 추가 할 때마다 새 개체를 추가하고 아직 사용하지 않은 개체를 추가해야합니다. 무슨 일이 일어나고 있는지 동일한 객체 사본을 추가하면 동일한 객체가 ArrayList의 다른 위치에 추가됩니다. 그리고 하나를 변경하면 동일한 사본이 계속 추가되기 때문에 모든 사본이 영향을받습니다. 예를 들어 다음과 같은 ArrayList가 있다고 가정합니다.

ArrayList<Card> list = new ArrayList<Card>();
Card c = new Card();

이제이 카드 c를 목록에 추가하면 문제없이 추가됩니다. 위치 0에 저장됩니다. 그러나 목록에 동일한 카드 c를 저장하면 위치 1에 저장됩니다. 따라서 목록의 서로 다른 두 위치에 동일한 1 개의 개체를 추가했습니다. 이제 Card 개체 c를 변경하면 위치 0과 1의 목록에있는 개체도 동일한 개체이므로 해당 변경 사항을 반영합니다.

한 가지 해결책은 Card 클래스에서 다른 Card 객체를 받아들이는 생성자를 만드는 것입니다. 그런 다음 해당 생성자에서 다음과 같이 속성을 설정할 수 있습니다.

public Card(Card c){
this.property1 = c.getProperty1();
this.property2 = c.getProperty2(); 
... //add all the properties that you have in this class Card this way
}

그리고 동일한 카드 사본 1 개가 있다고 가정 해 보겠습니다. 따라서 새 개체를 추가 할 때 다음과 같이 할 수 있습니다.

list.add(new Card(nameOfTheCardObjectThatYouWantADifferentCopyOf));

2

새 참조를 사용하는 대신 동일한 참조를 사용하는 결과 일 수도 있습니다.

 List<Foo> list = new ArrayList<Foo>();        

 setdata();
......

public void setdata(int i) {
  Foo temp = new Foo();
  tmp.setValue(i);
  list.add(tmp);
}

대신에:

List<Foo> list = new ArrayList<Foo>(); 
Foo temp = new Foo();       
setdata();
......

public void setdata(int i) {
  tmp.setValue(i);
  list.add(tmp);
} 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.