수색…


비고

변경할 수없는 객체는 고정 된 상태 (설정 자 없음)이므로 객체 생성시 모든 상태를 알아야합니다.

기술적으로 요구되지는 않지만 모든 필드를 final 으로 만드는 것이 좋습니다. 이것에 의해, 불변 클래스가 thread 세이프가됩니다 (Java Concurrency in Practice, 3.4. 1을 참조).

이 예제는이를 달성하는 데 도움이되는 몇 가지 패턴을 보여줍니다.

방어적인 복사를 사용하여 유형의 불변 버전 생성.

Java의 일부 기본 유형 및 클래스는 근본적으로 변경 가능합니다. 예를 들어 모든 배열 유형은 변경 가능하며 java.util.Data 와 같은 클래스도 변경할 수 있습니다. 변경 불가능한 유형이 요구되는 상황에서는 어색 할 수 있습니다.

이를 처리하는 한 가지 방법은 변경 가능한 유형에 대한 불변 래퍼를 만드는 것입니다. 다음은 정수 배열에 대한 간단한 래퍼입니다.

public class ImmutableIntArray {
    private final int[] array;
    
    public ImmutableIntArray(int[] array) {
        this.array = array.clone();
    }

    public int[] getValue() {
        return this.clone();
    }
}

이 클래스는 변형 복사 를 사용하여 변경 가능한 상태 ( int[] )를 변경시킬 수있는 코드에서 격리시킵니다.

  • 생성자는 clone() 을 사용하여 매개 변수 배열의 고유 한 복사본을 만듭니다. 이후 생성자의 호출자가 매개 변수 배열을 ImmutableIntArray 하면 ImmutableIntArray 의 상태에 영향을 미치지 않습니다.

  • getValue() 메서드는 또한 clone() 을 사용하여 반환되는 배열을 만듭니다. 호출자가 결과 배열을 변경하면 ImmutableIntArray 의 상태에 영향을 미치지 않습니다.

wrapped 배열에 대해 읽기 전용 연산을 수행하기 위해 메소드를 ImmutableIntArray 에 추가 할 수도 있습니다. 예를 들어, 길이를 얻거나, 특정 인덱스에서 값을 얻는 등등.

이 방법으로 구현 된 불변의 래퍼 형은, 원래의 형태와 형태가 호환되지 않습니다. 단순히 후자 대신 사용할 수는 없습니다.

변경 불가능한 클래스의 레시피

불변 개체는 상태를 변경할 수없는 개체입니다. 불변의 클래스는, 그 인스턴스가 설계 및 구현에 의해 불변 인 클래스입니다. 불변성의 예로 가장 일반적으로 제시되는 Java 클래스는 java.lang.String 입니다.

다음은 고정 관념의 예입니다.

public final class Person {
    private final String name;
    private final String ssn;     // (SSN == social security number)

    public Person(String name, String ssn) {
        this.name = name;
        this.ssn = ssn;
    }

    public String getName() {
        return name;
    }
   
    public String getSSN() {
        return ssn;
    }
}

이에 대한 변형은 생성자를 private 으로 선언하고 public static factory 메소드를 대신 제공하는 것입니다.


변경 불가능한 클래스의 표준 래서 피는 다음과 같습니다 :

  • 모든 속성은 생성자 또는 팩토리 메서드에서 설정해야합니다.
  • 세터가 없어야합니다.
  • 인터페이스 호환성을 위해 setter를 포함해야하는 경우 아무 것도하지 않거나 예외를 throw해야합니다.
  • 모든 속성은 privatefinal 로 선언해야합니다.
  • 변경 가능한 유형에 대한 참조 인 모든 특성의 경우 :
    • 속성은 생성자를 통해 전달 된 값의 전체 복사본으로 초기화해야하며
    • 해당 속성의 getter는 속성 값의 전체 복사본을 반환해야합니다.
  • 클래스는 불변의 클래스의 변경 가능한 서브 클래스를 작성하는 것을 막기 위해서 final 로서 선언 할 필요가 있습니다.

주목해야 할 몇 가지 사항 :

  • 불변성으로 인해 개체가 nullable이 될 수 있습니다. nullString 변수에 할당 할 수 있습니다.
  • 변경 불가능한 클래스 속성이 final 로 선언되면 인스턴스는 본질적으로 스레드로부터 안전합니다. 이렇게하면 불변 클래스가 멀티 스레드 응용 프로그램을 구현하기위한 좋은 구성 요소가됩니다.

클래스가 변경되지 못하게하는 전형적인 디자인 결함

생성자에서 필요한 모든 속성을 설정하지 않고 일부 설정자를 사용합니다.

public final class Person { // example of a bad immutability
    private final String name;
    private final String surname;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
    public String getSurname() { return surname;}
    public void setSurname(String surname) { this.surname = surname); }
}

Person 클래스가 변경 불가능하다는 것을 보여주기 쉽습니다.

Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation

이 문제를 해결하려면 setSurname() 삭제하고 다음과 같이 생성자를 리팩토링하십시오.

public Person(String name, String surname) {
    this.name = name;
    this.surname = surname;
  }

인스턴스 변수를 비공개 및 최종으로 표시하지 않음

다음 클래스를 살펴보십시오.

public final class Person {
    public String name;
    public Person(String name) {
        this.name = name;
     }
    public String getName() {
        return name;
    }
    
}

다음 스 니펫은 위의 클래스가 변경 불가능하지 않다는 것을 보여줍니다.

Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation

이를 수정하려면 name 속성을 privatefinal 로 표시하면됩니다.


getter에 클래스의 변경 가능한 객체 노출

다음 클래스를 살펴보십시오.

import java.util.List;
import java.util.ArrayList;
public final class Names {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }
    public List<String> getNames() {
        return names;
    }
    public int size() {
        return names.size();
    }
}

Names 클래스는 첫눈에 불변 인 것처럼 보이지만 다음 코드와 같이 아닙니다.

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

getNames() 의해 반환 된 참조 목록을 변경하면 실제 Names 목록을 수정할 수 있기 때문에 이런 일이 발생했습니다.

다음과 같이이 문제를 해결하려면, 방어 복사본을 만들어 클래스의 변경 가능한 객체를 참조하는 참조를 반환하지 않도록 :

public List<String> getNames() {
   return new ArrayList<String>(this.names); // copies elements
}

또는 다음과 같이 다른 불변 객체프리미티브 만 리턴되는 방식으로 getter를 설계함으로써 :

public String getName(int index) {
    return names.get(index);
}
public int size() {
    return names.size();
}

변경 불가능한 클래스 외부에서 수정할 수있는 객체로 생성자 삽입

이것은 이전의 결함의 변형입니다. 다음 클래스를 살펴보십시오.

import java.util.List;
public final class NewNames {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = names;
    }
    public String getName(int index) {
        return names.get(index);
    }
    public int size() {
        return names.size();
    }
}

이전의 Names 클래스처럼, NewNames 클래스도 NewNames 불변으로 보이지만 실제로는 다음 코드가 반대가됩니다.

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
NewNames names = new NewNames(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

이 문제를 해결하려면 이전 결함과 마찬가지로 개체를 불변 클래스에 직접 할당하지 않고 방어 복사본을 만드십시오. 즉, 생성자를 다음과 같이 변경할 수 있습니다.

    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }

클래스의 메소드를 오버라이드시키는 방법

다음 클래스를 살펴보십시오.

public class Person {
    private final String name;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
}

Person 클래스는 처음에는 불변으로 보이지만, Person 의 새로운 서브 클래스가 정의되었다고 가정 해보십시오 :

public class MutablePerson extends Person {
    private String newName;
    public MutablePerson(String name) {
        super(name);            
    }
    @Override
    public String getName() {
        return newName;
    }
    public void setName(String name) {
        newName = name;
    }
}

Person (im) mutability는 새로운 하위 클래스를 사용하여 다형성을 통해 악용 될 수 있습니다.

Person person = new MutablePerson("Average Joe");
System.out.println(person.getName()); prints Average Joe
person.setName("Magic Mike"); // NOT OK, person has now a new name!
System.out.println(person.getName()); // prints Magic Mike    

이 문제를 해결, 같은 클래스 표시하려면 final 그래서 그것을 확장 할 수 없습니다 또는 생성자 (들)의 모든 선언 private .



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow