클래스에서 개인 필드가 수정되는 것을 어떻게 방지합니까?


165

이 수업이 있다고 상상해보십시오.

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public String[] getArr() 
  {
    return arr;
  }
}

이제 위의 클래스를 사용하는 다른 클래스가 있습니다.

Test test = new Test();
test.getArr()[0] ="some value!"; //!!!

그래서 이것은 문제입니다 : 나는 외부에서 클래스의 개인 필드에 액세스했습니다! 이 문제를 어떻게 방지 할 수 있습니까? 이 배열을 어떻게 불변으로 만들 수 있습니까? 이것은 모든 getter 메소드에서 개인 필드에 액세스하기 위해 노력할 수 있음을 의미합니까? (구아바와 같은 라이브러리는 원하지 않습니다. 올바른 방법을 알아야합니다.)


10
실제로 는 필드를 수정final 하지 못합니다 . 그러나 필드에 의해 참조 되는 수정을 방지하는 것이 더 복잡합니다.Object
OldCurmudgeon

22
개인 필드에 저장된 참조가있는 배열을 수정할 수 있다고 생각하면 개인 필드를 수정할 수 있다고 생각하면 정신 모델에 문제가 있습니다.
조렌

45
비공개 인 이유는 무엇입니까?
에릭 레펜

7
이것을 알아내는 데 시간이 걸렸지 만 모든 데이터 구조가 객체 내에 완전히 캡슐화되어 있으면 항상 내 코드가 향상됩니다. 클래스 또는 반복자를 제공하십시오.
빌 K

8
다른 사람이 왜이 질문에 많은 관심을 기울였습니까?
로마

답변:


163

배열 의 사본 을 반환해야합니다 .

public String[] getArr() {
  return arr == null ? null : Arrays.copyOf(arr, arr.length);
}

121
나는 이것이 질문에 대한 정답으로 투표되었지만 문제에 대한 가장 좋은 해결책 은 실제로 sp00m 이-를 반환하는 것 Unmodifiable List입니다.
OldCurmudgeon

48
실제로 배열을 반환 해야하는 경우는 아닙니다.
Svish

8
실제로, 논의 된 문제에 대한 최상의 해결책 은 종종 @MikhailVladimirov가 말한 것처럼 :- 배열 또는 컬렉션 의 를 제공하거나 반환하는 것 입니다.
Christoffer Hammarström

20
그러나 데이터의 얕은 사본이 아닌 깊은 사본인지 확인하십시오. 문자열의 경우 아무런 차이가 없으며 인스턴스의 내용을 수정할 수있는 Date와 같은 다른 클래스의 경우 차이를 만들 수 있습니다.
jwenting

16
@ Svish 나는 더 과감했습니다 .API 함수에서 배열을 반환하면 잘못하고 있습니다. 라이브러리 내부의 개인 함수에서는 맞을 있습니다. API (수정 성 방지 기능이 중요한 역할)에서는 결코 그렇지 않습니다 .
Konrad Rudolph

377

배열 대신 List를 사용할 수있는 경우 Collections는 수정할 수없는 목록을 제공합니다 .

public List<String> getList() {
    return Collections.unmodifiableList(list);
}

Collections.unmodifiableList(list)클래스 자체에 의해 목록이 변경되지 않도록 필드에 할당으로 사용 되는 답변추가했습니다 .
Maarten Bodewes

이 방법을 디 컴파일하면 많은 새로운 키워드를 얻게됩니다. getList ()가 많이 액세스 할 때 성능이 저하되지 않습니다. 예를 들어 렌더링 코드에서.
Thomas15v

2
이것은 배열의 요소가 그대로 불변 인 경우 작동 String하지만 변경 가능한 경우 호출자가 변경하지 못하도록 무언가를 수행해야 할 수도 있습니다.
Lii 2019

45

수정 private자는 필드 자체 만 다른 클래스에서 액세스하지 못하도록 보호하지만이 필드의 객체 참조는 아닙니다. 참조 된 개체를 보호해야하는 경우에는 제공하지 마십시오. 변화

public String [] getArr ()
{
    return arr;
}

에:

public String [] getArr ()
{
    return arr.clone ();
}

또는

public int getArrLength ()
{
    return arr.length;
}

public String getArrElementAt (int index)
{
    return arr [index];
}

5
이 기사는 배열에서 클론을 사용하는 것에 반대하지 않으며 서브 클래스 화 될 수있는 객체에서만 사용합니다.
Lyn Headley

28

Collections.unmodifiableList이미 언급 된 - Arrays.asList()이상하게 없습니다! 내 해결책은 외부에서 목록을 사용하고 다음과 같이 배열을 래핑하는 것입니다.

String[] arr = new String[]{"1", "2"}; 
public List<String> getList() {
    return Collections.unmodifiableList(Arrays.asList(arr));
}

배열 복사의 문제점은 코드에 액세스 할 때마다 배열을 수행하고 배열이 큰 경우 가비지 수집기에 대해 많은 작업을 수행한다는 것입니다. 따라서 사본은 간단하지만 실제로 나쁜 접근 방식입니다. "저렴한"이라고 말하지만 메모리가 비쌉니다! 특히 2 개 이상의 요소가있는 경우.

당신의 소스 코드를 보면 Arrays.asListCollections.unmodifiableList이 실제로 많이 만들어지지 않습니다. 첫 번째는 복사하지 않고 배열을 래핑하고 두 번째는 목록을 래핑하여 변경 사항을 사용할 수 없게합니다.


여기서는 문자열을 포함한 배열 이 매번 복사 된다고 가정합니다 . 변경 불가능한 문자열에 대한 참조 만 복사되는 것은 아닙니다. 배열이 크지 않은 한 오버 헤드는 무시할 수 있습니다. 배열이 거대하다면 목록과 immutableList끝까지 가십시오 !
Maarten Bodewes

아니, 나는 그런 가정을하지 않았다-어디? 복사 된 참조에 관한 것입니다-그것이 String 또는 다른 객체인지는 중요하지 않습니다. 큰 배열의 경우 배열을 복사하지 않는 것이 좋으며 더 큰 배열의 경우 Arrays.asListCollections.unmodifiableList연산이 저렴 할 것입니다. 어쩌면 누군가가 필요한 요소 수를 계산할 수는 있지만 이미 수정을 막기 위해 복사되는 수천 개의 요소가 포함 된 배열이있는 for-loops의 코드를 이미 보았습니다.
michael_s

6

ImmutableList표준보다 나은 것을 사용할 수도 있습니다 unmodifiableList. 이 수업은 Google 에서 만든 구아바 라이브러리의 일부입니다 .

설명은 다음과 같습니다.

여전히 변경할 수있는 별도의 컬렉션에 대한보기 인 Collections.unmodifiableList (java.util.List)와 달리 ImmutableList의 인스턴스에는 자체 개인 데이터가 포함되어 있으며 절대 변경되지 않습니다.

사용 방법에 대한 간단한 예는 다음과 같습니다.

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public ImmutableList<String> getArr() 
  {
    return ImmutableList.copyOf(arr);
  }
}

반환 된 목록을 변경 하지 않고 Test클래스를 신뢰할 수없는 경우이 솔루션을 사용하십시오 . 이를 ImmutableList리턴하고 목록의 요소도 변경할 수 없는지 확인해야합니다 . 그렇지 않으면 내 솔루션 도 안전해야합니다.
Maarten Bodewes

3

이 관점에서 시스템 배열 사본을 사용해야합니다.

public String[] getArr() {
   if (arr != null) {
      String[] arrcpy = new String[arr.length];
      System.arraycopy(arr, 0, arrcpy, 0, arr.length);
      return arrcpy;
   } else
      return null;
   }
}

-1을 호출하는 동안 아무것도하지 않으며 clone간결하지 않습니다.
Maarten Bodewes

2

데이터 사본을 리턴 할 수 있습니다. 데이터 변경을 선택한 발신자는 사본 만 변경합니다.

public class Test {
    private static String[] arr = new String[] { "1", "2" };

    public String[] getArr() {

        String[] b = new String[arr.length];

        System.arraycopy(arr, 0, b, 0, arr.length);

        return b;
    }
}

2

문제의 핵심은 변경 가능한 객체에 대한 포인터를 반환한다는 것입니다. 죄송합니다. 객체를 불변으로 (수정 불가능한리스트 솔루션) 렌더링하거나 객체의 복사본을 반환합니다.

일반적으로 객체의 최종성은 객체가 변경 가능한 경우 변경되는 것을 방지하지 않습니다. 이 두 가지 문제는 "사촌 키스"입니다.


Java에는 참조가 있고 C에는 포인터가 있습니다. 충분했다. 또한 "최종"수정자는 적용 위치에 따라 여러 영향을 미칠 수 있습니다. 클래스 멤버에 적용하면 "="(할당) 연산자를 사용하여 멤버에게 정확히 한 번만 값을 할당해야합니다. 이것은 불변성과 관련이 없습니다. 멤버 참조를 새 멤버 (같은 클래스의 다른 인스턴스)로 바꾸면 이전 인스턴스는 변경되지 않고 그대로 유지됩니다 (그러나 다른 참조를 보유하고 있지 않은 경우 GC 될 수 있음).
gyorgyabraham 2019

1

수정 불가능한 목록을 반환하는 것이 좋습니다. 그러나 getter 메소드 호출 중에 수정할 수없는 목록은 클래스 또는 클래스에서 파생 된 클래스에 의해 여전히 변경 될 수 있습니다.

대신 클래스를 확장하는 사람에게 목록을 수정해서는 안된다는 것을 분명히해야합니다.

따라서 귀하의 예에서 다음 코드로 이어질 수 있습니다.

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Test {
    public static final List<String> STRINGS =
        Collections.unmodifiableList(
            Arrays.asList("1", "2"));

    public final List<String> getStrings() {
        return STRINGS;
    }
}

위의 예에서 나는 STRINGS 필드를 공개적으로 원칙적으로 값을 이미 알고 있으므로 메소드 호출을 제거 할 수 있습니다.

private final List<String>클래스 인스턴스 생성 중에 수정할 수없는 필드에 문자열을 할당 할 수도 있습니다 . (생성자의) 상수 또는 인스턴스화 인수를 사용하는 것은 클래스 디자인에 달려 있습니다.

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Test {
    private final List<String> strings;

    public Test(final String ... strings) {
        this.strings = Collections.unmodifiableList(Arrays
                .asList(strings));
    }

    public final List<String> getStrings() {
        return strings;
    }
}

0

예, 배열의 사본을 반환해야합니다.

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.