작지만 꾸준한 반복

Java final과 불변, 그리고 불변 객체 본문

공부기록/Java

Java final과 불변, 그리고 불변 객체

iamjooon2 2023. 3. 29. 00:00

Java의 final은 크게 변수, 메서드, 클래스에 붙여줄 수 있는 키워드이다

이 final이 붙은 대상은, 재할당이 불가능하다 (불변을 의미하진 않는다)

 

final은 변수에서 다음과 같이 사용할 수 있다

예시는 자동차 경주 게임이다.

public class Car {
    private static final THROTTLE = 4; // 상수에 사용
    
    private final String owner; // 재할당 불가
    private int position; // 재할당 가능
    
    public Car(final String name) { // 파라미터에도 붙일 수 있음
        this.owner = name;
        this.position = 0;
    }
    
    public void move(final int power) {
        if (power > THROTTLE) {
            position++;
        }
    }
    
}

 

상수로 사용한 THROTTLE 부터 보자.

객체 내부에서만 사용할지, 외부에서도 사용할지에 따라 접근 제어자를 설정해두고

static을 사용해 메모리에 미리 적재시킨 다음,

final을 사용하여 재할당을 불가능하도록 하였다.

이처럼 상수를 만들 때 final을 사용할 수 있다

 

두번째로, final을 붙인 변수인 owner를 보자.

자동차 경주 게임에서 차의 이름은 생성 후 바뀌지 않아, final을 붙여 재할당을 막았다.

만일 final이 붙은 변수에 값을 재할당 하려고 할 시, 컴파일 타임에서 에러가 발생한다.

 

세번째로 position을 보자.

position은 final 키워드를 붙이지 않았다.

자동차는 메서드로 받은 power의 값에 따라 position이 증가할 수 있어, 재할당 가능하기 때문이다

이처럼 final을 붙이지 않은 변수는 재할당 가능하다.

 

추가로 메서드 파라미터에도 final을 붙일 수 있다

Argument로 넘어온 변수의 재할당을 막기 위해 사용하였다.

 

final은 메서드에도 사용할 수 있다

public class Parent {
    public final void nonOveridableFunction() {
        // do something...
    }
}

public class Child extends Parent {
   void nonOveridableFunction() { 
      // 불가
   } 
}

 

메서드에 final을 사용하면, 해당 메서드의 오버라이딩을 막을 수 있다

역시 컴파일타임에서 에러를 발생시킨다.

 

 

이 final은 클래스에서도 사용가능하다!

public final class Parent {

}

public class Child extends Parent {
    // 불가
}

클래스에서 사용한다면, 해당 클래스의 상속을 막을 수 있다.

 

서두에 final은 재할당 불가의 의미이지, 불변을 뜻하지는 않는다고 적어놨다

 

아니 지금 쓴거 보면 충분히 불변할 것 같은데 왜?

final List<Integer> numbers = new ArrayList<>();
System.out.println(numbers); // []

numbers.add(1);
System.out.println(number); // [1]

numbers = new ArrayList<>(); // 컴파일 에러

분명 final을 붙였는데 내부 상태의 값이 바뀐 것을 알 수 있다!

 

그 이뉴는 numbers가 참조하는 ArrayList 객체의 내부 상태를 변경하는 것은 가능하기 때문이다.

numbers.add(1)의 코드에서는 ArrayList 객체의 내부 상태가 변경되어 [1] 이라는 새로운 리스트가 만들어졌다.

final을 붙였지만, 완전한 불변을 보장하지 않는 셈이다!

반대로 numbers = new ArrayList<>(); 에서는 final이 적용된 변수에 새 구현체를 할당하려하니 컴파일 에러가 발생한다.

 

결국, final은 변수를 재할당하는 것을 방지해 변수의 불변성을 보장할 수 있지만,

해당 변수가 참조하는 객체의 내부 상태는 변경 가능하므로, 완전한 불변을 보장하지는 않는다.

객체의 변수에 final을 붙여준다 해도, 해당 변수가 참조하는 주소값만 불변이고, 그 객체가 또 다시 참조한 주소값은 불변이 아니기 때문이다. (일종의 shallow copy라는 생각이 든다)

 

그렇다면, 객체에서 불변을 보장하는 방법은 무엇이 있을까?

 

1.