[Java] final 어따씀?

이신영·2024년 5월 30일
1

Java

목록 보기
4/11
post-thumbnail
post-custom-banner

친구들이랑 게임을 하다보면 찐막? 이라고 한판 더하는게 국룰이다. (물론 찐*N + 막 이 있긴하지만)

저말은 곧? "이것만하고 무조건 끈다!" 라는 거다. final이 그렇다 ㅎㅎ..


우리는 final을 언제 썼을까?

스프링을 쓰다보면 생성자주입(DI)을 하기위해 final을 사용하여 주입하는 경우가 많다.


@Service
@RequiredArgsConstructor
public class UserService {

	//final을 활용한 생성자 주입
    private final UserRepository userRepository;
    private final MemberService memberService;

    public void register(String name) {
        userRepository.add(name);
    }

}

final은 "최종적인" 이라는 의미이다.
즉, 해당 객체에 값이 저장되면 최종적인 값이 되므로, 수정이 불가능하다는 의미이다.


final의 종류

final 키워드는 변수, 메서드, 클래스를 선언할 때 사용되며 모두 재할당이 불가능하다는 특징이 있다.

1. 변수에서 final

final 변수의 값은 한 번만 설정할 수 있음을 의미하고 초기화 이후에는 값을 변경할 수 없다. 이는 데이터의 불변성을 보장하고, 코드의 예측 가능성안정성을 높여준다.

public class FinalVariableExample {
    private final int number;

    public FinalVariableExample(int number) {
        this.number = number;  // 생성자에서 초기화
    }

    public static void main(String[] args) {
        final int number = 10;
        number = 20;  // 컴파일 오류: final 변수는 값을 변경할 수 없다.
    }
}

사용 예시

  • 상수 선언: 수학에서의 원주율(π) 같은 변하지 않는 값을 상수로 선언할 때 사용한다.
  • 의존성 주입(DI): 스프링 프레임워크에서 주입된 객체가 변경되지 않도록 보장하기 위해 사용한다.

2. 메서드에서 final

final 메서드는 서브클래스에서 오버라이드할 수 없다. 이는 메서드의 동작을 보장하고, 의도치 않은 수정으로 인한 오류를 방지할 수 있다.

public class ParentClass {
    public final void display() {
        System.out.println("This is a final method.");
    }
}

public class ChildClass extends ParentClass {
    // 컴파일 오류: final 메소드는 오버라이드할 수 없다.
    @Override
    public void display() {  
        System.out.println("Trying to override.");
    }
}

사용 예시

  • 보안: 중요한 메소드가 서브클래스에 의해 변경되지 않도록 보장한다.
  • API 안정성: 공개 API에서 의도한 동작을 유지하고 변경되지 않도록 보장한다.

3. 클래스에서 final

final 클래스는 상속될 수 없다. 이는 클래스의 구현을 고정하고, 확장을 통한 변경을 방지한다.

public final class FinalClass {
    // 클래스 내용
}

// 컴파일 오류: final 클래스는 상속할 수 없다.
public class SubClass extends FinalClass {  
    // 서브클래스 내용
}

사용 예시

  • 불변 클래스: 불변 객체를 만들기 위해 클래스 자체를 불변으로 선언한다. 예를 들어, String 클래스는 final로 선언되어 있다.
  • 라이브러리 클래스: 라이브러리나 API의 특정 클래스가 확장되지 않도록 하여, 안정성과 일관성을 유지한다.

그래서 요약하자면

  • 변수에 붙인 경우에는 변경할 수 없는 상수가 된다.
  • 메서드에 붙인 경우에는 오버라이딩이 불가능하다.
  • 클래스에 붙인 경우에는 상속이 불가능하다.

즉, 재할당이 불가능해진다.


그래서 왜 씀 ?

1. 변경 가능성을 최소화

혹여나 누군가가 해당 변수를 변경하는 경우 컴파일타임에서 오류가 발생하기 때문에 값이 변경되는것을 쉽게 감지할 수 있고 방지하기도 편하다.

2. thread safe를 지킬 수 있다.

동시성 환경(멀티 쓰레드 환경)에서 final로 불변객체를 만들어서 여러 스레드가 동시에 접근해도 동일한 값을 보장받을 수 있다.

3. 간접적으로 메모리 성능을 높인다

객체의 변화를 감지할 필요가 없으므로 가비지 컬렉터가 객체를 스캔할때와 제거할 때의 횟수가 줄어들기 때문에 성능적인 이점을 보인다.


컴파일러 시점에서 final?

final을 쓰는 경우 컴파일 시에 조금 특별한 일이 벌어진다.

public class Example {
    public static final String CONSTANT = "상수";
}


public class Test {
    public void printConstant() {
        System.out.println(Example.CONSTANT); 
        // "상수" 문자열이 직접 코드에 삽입됨
    }
}

이는 컴파일타임에서 최적화가 진행되기 때문이다.

컴파일 타임

상수로 최적화 : 컴파일러는 final 변수는 컴파일러가 컴파일 시점에 해당 값을 알고 있으며, 이 값을 코드에 직접 삽입할 수 있다. 이는 해당 값을 사용하는 모든 코드에 실제 값이 삽입된다는 것을 의미한다.

런타임

불변성 유지 : final 변수는 초기화 후 변경할 수 없으므로 런타임에 final 변수의 값을 변경하지 않음을 보장한다.

메서드 호출 최적화 : final 메서드는 오버라이드될 수 없으므로, JVM은 메서드 호출을 정적 바인딩으로 처리할 수 있다. 이는 런타임에 메서드 호출 성능을 향상시킨다.


번외) static final?

static final은 클래스 수준의 변수로서 메모리에 단 한 번 할당되고, 그 이후에는 값이 변경되지 않는 특성을 가지고 있다.

static : 객체마다 가질 필요가 없는 공용으로 사용하는 필드 혹은 인스턴스 필드를 포함하지 않는 메소드
final : 한 번 값이 정해지고 나면 값을 바꿀 수 없는 필드
static final : 모든 영역에서 고정된 값으로 사용하는 상수

profile
후회하지 않는 사람이 되자 🔥
post-custom-banner

0개의 댓글