6. 불변 객체 (Immutable Object) & Final

2023. 9. 3. 20:58Java

불변 객체 (Immutable Object)

객체 생성 후 내부의 상태를 바꿀 수 없는 객체 & 
생성된 객체가 내부 상태를 변경할 수 없게 만들어진 객체

 

대표적인 불변 객체 : String

String str ="a";  //(1)
str = "b";   //(2)
str = "c";   //(3)

(1) heap영역에 str을 참조하는 "a"라는 String객체가 생성

(2) "a"객체가 "b" 값으로 변하는 것이 아니라 "b"라는 새로운 객체가 생성되고, str은 "b" 객체를 바라본다.

(3) (2)와 마찬가지로 "c" 객체가 새로 생성, str은 "c"를 바라본다.

 

불변 객체의 장점

1. 객체에 대한 신뢰도 높아짐

  • 생성된 객체의 내부가 변하지 않음

 

2. Thread-Safe

  • 동기화 문제가 발생하는 이유는 공유 자원에 동시 접근하여 쓰기 때문인데,
  • 항상 같은 값만 반환하므로 동기화를 고려할 필요가 없어 프로그램의 실행에 문제가 없다.

 

3. 실패 원자적인 메소드를 만들 수 있다

  • 가변 객체는 예외 발생 시 불안정한 상태에 빠질 수 있고 그로인해 다른 에러가 발생할 수 있다.
  • 불변 객체는 예외가 발생하더라도 메소드 호출 전의 상태를 유지할 수 있으므로, 예외가 발생하여도 변경된 상태로 인한 추가 에러를 막을 수 있다.

 

4. 다른 사람이 작성한 함수가 예측 가능하다

 

불변 객체를 어떻게 만드는가?

=> 내부 값을 변경할 수 없게 만들어야 한다.

 

  • 원시 타입만 있는 경우

 

1. 클래스를 final 선언

2. 모든 클래스 변수를 private과 final로 선언

3. setter 메소드 사용하지 않는다.

public final class Number {
    private final int number;

    //생성자에서 필드로 값을 넘겨서 값을 초기화해주는 객체를 불변 객체라고 함.
    public Number(final int number){
        this.number = number;
    }

    public int getNumber() {
        return number;
    }
}

 

  • 참조 타입이 있는 경우
public class A {

    private final Age age;
    
    public A(final Age age){
        this.age = age;
    }

    public Age getAge() {
        return age;
    }
}

위와 같은 경우,

Age의 값이 변경될 수 있으므로 불변객체가 될 수 없다.

그러므로, 불변 객체의 참조 변수 또한 불변이여야한다.

 

1) 일반 객체일 때

public class ReferenceType {

    //    1. 참조 변수가 일반 객체일 때
    private final Age age;

    public ReferenceType(final Age age){
        this.age = age;
    }

    public Age getAge(){
        return age;
    }
}


//1. Age 클래스를 불변으로 만든다.
class Age {

    private final int value;

    public Age(final int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

2. Array일 때

public class ArrayType {

//    2. Array일 때
    private final int[] array;

    public ArrayType( final int[] array){
        this.array = Arrays.copyOf(array, array.length);
    }

    //2. 배열을 그대로 참조하거나, 반환할 경우 외부에서 배열 내부 값 변경 가능하므로 clone한 배열 값을 반환
    public int[] getArray(){
        return array.clone();
    }
}
public class ArrayTypeMain {

    public static void main(String[] args) {
        int[] array = {1,2,100};
        ArrayType arrayType = new ArrayType(array);

        for (int i : arrayType.getArray()
             ) {
            System.out.println(i + " ");
        };
//        결과 : 1,2,100

        array[0] = 2000; //array값을 바꿔도 arrayType의 값은 변하지 않는다.

        for (int i : arrayType.getArray()
        ) {
            System.out.println(i + " ");
        };
//        결과 : 1,2,100
    }
}

 

3. List일 때

public class ListType {

    private final List<Age> list;

    public ListType(final List<Age> list){
//      그대로 참조하지 않고, 새로운 list 객체를 만들어 사용
        this.list = new ArrayList<>(list);
    }


    public List<Age> getList() {
//        값 변경 불가능하도록 unmodifiableList() 메소드 사용
        return Collections.unmodifiableList(list);
    }
}
public class ListTypeMain {

    public static void main(String[] args) {
        List<Age> age = new ArrayList<>();
        age.add(new Age(30));

        ListType listType = new ListType(age);

        for (Age a: listType.getList()
             ) {
            System.out.println(a.getValue());
        }
//      결과 : 30

        System.out.println("---------------------");
        //age 리스트에는 값이 추가되지만, listType의 list에는 추가되지 않는다.
        age.add(new Age(100));

        for (Age a: listType.getList()
        ) {
            System.out.println(a.getValue());
        }
//      결과 : 30
    }
}

 

결론,

재할당은 가능하지만, 한번 할당하면 내부 데이터를 변경할 수 없다.

 

불변 객체를 왜 사용할까?

Q. setter가 없으면 불편하지 않을까? 왜 불변성이라는게 있을까?

 

A.

유지보수와 관련있다. 

Setter가 생기면 외부에 의해 값이 변경될 가능성이 있고, 변경된 값으로 인한 에러가 생기면 코드의 전반적인 부분을 살펴봐야한다.

Setter가 없으면 변경에 대해 닫혀있다. 메모리 비용은 들어도 코드로써의 비용은 줄어들게 된다.

 

Final

오직 한번만 할당할 수 있고, 항상 같은 값을 가진다.

1) final 클래스

상속을 금지시켜 부모가 될 수 없는 클래스

ex) String 클래스 => public class MyString extends String (불가)

 

2) final 메소드

오버라이딩 불가

 

 

3) final 변수

수정 불가

 

 

 
 

 

'Java' 카테고리의 다른 글

10. 예외처리와 enum  (0) 2023.09.10
9. 컬렉션 프레임 워크와 제네릭  (0) 2023.09.04
8. 익명 클래스와 람다식  (0) 2023.09.04
7. 자바 리플렉션 (Reflection)  (0) 2023.09.04
5. 추상클래스와 인터페이스  (0) 2023.09.03