불변 객체(Immnutable Object)란??

2023. 5. 27. 20:36JAVA

728x90

 

불변 객체란 뭘까??

 

불변 객체란 생성 후 그 상태를 바꿀 수 없는 객체를 말한다.

따라서, multi - thread 환경에서 안전하다!!

 

그렇다면, 아래의 코드는 불변 객체일까?? 아닐까?

 

public class Phone {

    private final String madeBy;
    private final Screen screen;
    private int useAge;

    public Phone(String madeBy, Screen screen, int useAge) {
        this.madeBy = madeBy;
        this.screen = screen;
        this.useAge = useAge;
    } 

	  private final String madeBy;

    private final Screen screen;

    private int useAge;

    public Phone(String madeBy, Screen screen, int useAge) {
        this.madeBy = madeBy;
        this.screen = screen;
        this.useAge = useAge;
    }

		getter, setter ... 
}
package immutableObject;

public class Screen {

    private int horizontalPixels;
    private int verticalPixels;

    public Screen (int horizontalPixels, int verticalPixels){
        this.horizontalPixels = horizontalPixels;
        this.verticalPixels = verticalPixels;
    }

    getter, setter ...
}

 

fianl 키워드를 사용했기 때문에 불변 객체라고 생각할 수 있지만, 아직까지는 불변객체가 아니다!

아래와 같이 getScreen() 메서드를 사용해서 screen 객체의 값을 변경할 수 있다.

 

public class PhoneTest {
    
    public static void main(String args[]){

	Phone phone = new Phone("samsung",new Screen(400,300),1);

        // phone.getMadeBy() = "apple"; -> 컴파일 에러
        phone.getScreen().setHorizontalPixels(1000);

        // phone.getUseAge() = 2; -> 컴파일 에러 
	}
}

 

Phone 객체의 madeBy 필드와 useAge 같은 경우에는 단순히 final과 setter를 생성하지 않는 것만으로도 불변의 형태를 완성시킬 수 있다.

 

하지만, screen과 같은 참조 타입은 추가적인 작업이 필요하다.

 

추가적인 작업이란 Screen 또한 불변 객체로 만드는 것이다.

 

public class Screen {

    private final int horizontalPixels;
    private final int verticalPixels;

    public Screen (int horizontalPixels, int verticalPixels){
        this.horizontalPixels = horizontalPixels;
        this.verticalPixels = verticalPixels;
    }

 

이렇게 하면, Phone은 불변객체가 될 수 있다!

 

하지만, 참조변수가 일반 객체가 아니라 배열일 경우에는 위와 같은 방식대로 하면, 불변객체가 될까?

 

public class Phones {

    private final List<Phone> phones;

    public Phones(List<Phone> phones) {
        this.phones = phones;
    }

    public List<Phone> getPhones() {
        return phones;
    }
}

 

public static void main(String args[]){

    List<Phone> phones = new ArrayList<>();
    phones.add(new Phone("samsung",new Screen(800,600),1));
    phones.add(new Phone("apple",new Screen(1000,800),2));

    Phones phones1 = new Phones(phones);

    for(Phone phone : phones1.getPhones()) {
        System.out.println(phone.toString());
    }

    phones.add(new Phone("Lg",new Screen(600,500),3));

    for(Phone phone : phones1.getPhones()) {
        System.out.println(phone.toString());
    }
}

 

아래와 같은 방식으로 Phones 타입의 객체를 만든다면, Phones 타입의 객체의 불변성은 깨지게 된다.

=> 현재 외부에서 phone 객체를 만들어 phones의 인스턴스 변수에 추가하고 있다.

 

어떻게 하면, Phones 타입의 객체의 불변성을 지킬 수 있을까?? 

 

현재, Phones타입의 생성자를 통해 외부에서 phone 객체를 만들어 추가하고 있다. 이를 방지하려면 생성자에 추가적인 조작이 필요해보인다.

 

 

답은 생성자를 통해 값을 전달받을 때 new ArrayList<>(cars)를 통해 새로운 값을 참조하도록 하는 것이다.

이렇게 되면, 외부에서 넘겨주는 cars와 내부의 cars가 다르기 때문에 외부에서 제어가 불가능합니다.

 

public class Phones {

    private final List<Phone> phones;

    public Phones(List<Phone> phones) {
        this.phones = new ArrayList<>(phones);
    }

    public List<Phone> getPhones() {
        return phones;
    }
}

 

하지만, 이렇게 해도 getter()를 사용해 여전히 외부에서 조작이 가능합니다.

 

public class PhoneTest {
    
    public static void main(String args[]){

        List<Phone> phones = new ArrayList<>();
        phones.add(new Phone("samsung",new Screen(800,600),1));
        phones.add(new Phone("apple",new Screen(1000,800),2));

        Phones phones1 = new Phones(phones);

        phones1.getPhones().add(new Phone("Lg",new Screen(600,600),3));

        for(Phone phone : phones1.getPhones()) {
            System.out.println(phone.toString());
        }
    }
}

 

이는 Collections이 제공하는 unmodifieableList() 메서드를 활용해서 해결할 수 있습니다.

 

public class Phones {

    private final List<Phone> phones;

    public Phones(List<Phone> phones) {
        this.phones = new ArrayList<>(phones);
    }

    public List<Phone> getPhones() {
        return Collections.unmodifiableList(phones);
    }
}

 

public class PhoneTest {
    
    public static void main(String args[]){

        List<Phone> phones = new ArrayList<>();
        phones.add(new Phone("samsung",new Screen(800,600),1));
        phones.add(new Phone("apple",new Screen(1000,800),2));

        Phones phones1 = new Phones(phones);

        phones1.getPhones().add(new Phone("Lg",new Screen(600,600),3));

        for(Phone phone : phones1.getPhones()) {
            System.out.println(phone.toString());
        }
    }
}

 

 

최종적으로, 불변 객체로 만들려고 하는 객체의 인스턴스 변수가 배열일 경우에는 생성자와 get() 메서드에 추가적인 작업이 필요합니다.

 

 

지금까지 불변객체란 무엇인가, 그리고 불변객체를 만드는 방법에 대해서 알아봤습니다. 

 

모두 열공하세요~

 

728x90