본문 바로가기
  • 시 쓰는 개발자
1일 1개념정리 (24년 8월~)/Java

1일1개 (16) - 자바 final

by poetDeveloper 2024. 8. 25.

1일 1개념정리 24.08.09.금 ~ 

 

큰 결정에 큰 동기가 따르지 않을 때도 있다. 하지만 큰 결심이 따라야 이뤄낼 수 있다.

무조건 무조건 1일 1개의 개념 정리하기 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!


#16. 자바 final

자바에서 보이는 이 final은 변수에서만 사용된다고 오해할 수 있는데, 아니다. 변수, 메소드, 클래스에 적용될 수 있으며, 각각 다른 의미를 가진다. 오늘은 final에 대해 알아봅시다.

 

일단 final은 한마디로 변경이 불가능하도록 만들어주는 키워드고 상황에 따라 다르게 활용된다 !!

 

일반 변수 final

일반변수에 final을 쓰면 그 변수의 값을 한 번 할당하면 이후에는 변경할 수 없다. 마치 상수로 취급할 때 유용하다.

  • 로컬 변수 : 메소드 내부에서 정의된 변수에 final을 사용할 수 있다.
  • 필드 : 클래스의 멤버 변수(필드)에도 final을 사용할 수 있다.
public class ex {
    public static void main(String[] args) {
        final int NUMBER = 10; // 상수 선언 및 초기화
        // NUMBER = 20; // 오류 : NUMBER는 final로 선언되어 값을 변경할 수 없음
    }
}

참조 변수 final

객체에 대한 참조 변수를 final로 선언하면, 참조 자체는 변경할 수 없지만, 참조된 객체의 상태는 변경할 수 있다. (중요)

class MyClass {
    int value = 10;
}

public class ex {
    public static void main(String[] args) {
        final MyClass obj = new MyClass();
        obj.value = 20; // 객체의 상태는 변경 가능
        // obj = new MyClass(); // 오류 : 참조를 다른 객체로 변경할 수 없음
    }
}

메소드 final

final을 "메소드에" 쓰면 그 메소드는 서브클래스에서 오버라이딩(재정의)할 수 없다. 이는 메소드의 동작이 변경되지 않도록 보장할 때 유용하다.

class ParentClass {
    public final void showMessage() // final 설정
    {
        System.out.println("This is a final method.");
    }
}

class ChildClass extends ParentClass {
    // public void showMessage() {
    //     System.out.println("Attempting to override.");
    // }
    // 오버라이딩 못함
}

클래스 final

final을 "클래스에" 쓰면 그 클래스를 상속할 수 없다. 즉, 이 클래스는 서브클래스를 가질 수 없다. 특정 클래스의 기능을 변경하지 않도록 보장할 때 사용된다.

final class FinalClass {
    public void showMessage() {
        System.out.println("This is a final class.");
    }
}

// class ExtendedClass extends FinalClass {
//     // 오류 : FinalClass는 final로 선언되어 상속할 수 없음
// }

정리

  • 변수에 final : 변수의 값이 한 번 초기화된 이후 변경 불가능
  • 메소드에 final : 해당 메소드는 서브클래스에서 오버라이딩 불가능 (재정의 X)
  • 클래스에 final : 해당 클래스는 상속될 수 없게 된다.

그래서 다음과 같이 활용하면 좋다.

  • 상수 값을 선언할 때 final을 사용하여 코드의 가독성과 유지보수성을 높인다.
  • 변경하면 안 되는 메소드나 클래스를 final로 선언해, 코드의 안정성을 확보한다.
  • 참조의 불변성을 보장하기 위해 final 참조 변수를 사용하는 것도 좋다.

 

C언어 const    VS    Java final 비교

읽다보면 C언어에서 말하는 const랑 비슷한 느낌이 든다. 비교해봅시다. 변수 사용에 있어서는 비슷한 맥락인데, Java에서 좀 더 다채롭게 사용하는 느낌이다.

 

final

  • 변수 : 한 번 초기화된 후에는 값이 변경되지 않음을 보장
  • 메소드 : 오버라이딩을 금지
  • 클래스 : 상속을 금지

const

  • 변수 : 해당 변수의 값이 수정되지 않음을 보장
  • 포인터 : 포인터 자체가 가리키는 값을 수정하지 못하게 하거나, 포인터가 가리키는 주소 자체를 변경하지 못하게 할 수 있다.
case 1. 포인터가 가리키는 값이 변경되지 않게
const int *ptr = &number;
// *ptr = 20; // 오류


case 2. 포인터 자체를 변경하지 못하게
int *const ptr = &number;
// ptr = &otherNumber;


case 3. 포인터도 변경하지 못하고, 가리키는 값도 변경하지 못하게
const int *const ptr = &number;
// *ptr = 20; // 오류
// ptr = &otherNumber; // 오류

 

주의사항

  • final로 선언된 일반변수는 반드시 초기화되어야 함. ★ ★ ★
  • final로 선언된 참조변수는 객체의 필드는 변경될 수 있음을 유의해야 함
  • final로 선언된 메소드는 상속받는 클래스에서 오버라이딩할 수 없다.
  • final 클래스는 상속할 수 없으므로, 그 클래스를 확장하려는 시도 자체를 피해야함

 

여담 - Spring에선 왜 필드를 맨날 private final로 초기화하는 것 ??

예를들어.... 우린 다음과 같은 구조의 코드를 자주 본다.

@Service
public class MyService {

    private final MyRepository myRepository;

    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}

 

Spring에서 위처럼 final로 쓰는 이유는 DI와 관련있다. 일단 기본적으로 앞선 맥락과 같다. final을 쓰면 해당 필드는 한 번 초기화된 후 변경할 수 없다. 이는 객체의 상태를 불변으로 만들어 의도치 않은 변경을 방지하고, 코드의 안정성을 높여준다. 그리고 DI 측면도 있다.

 

의존성 주입 에서의 안전성

  • Spring에서 DI로 의존성을 주입받을 때, final로 선언된 필드를 사용하면 주입된 객체가 변경되지 않음을 보장할 수 있다.
  • 위에 주의사항에서 언급했듯이 final은 반드시 초기화되어야한다. 그래서 생성자 주입을 써서 final 필드를 초기화하면 해당 필드가 반드시 생성자 호출시 초기화 되므로 NullPointerException을 피할 수 있다.

→ 결론적으로 안전한 DI가 가능해진 것이다 !!!!!

'1일 1개념정리 (24년 8월~) > Java' 카테고리의 다른 글

1일1개 (19) - static  (0) 2024.08.28
1일1개 (18) - 상속과 구현  (0) 2024.08.27
1일1개 (17) - 오버라이딩 vs 오버로딩  (0) 2024.08.26
1일1개 (5) - 인터페이스  (2) 2024.08.13
1일1개 (4) - JVM  (2) 2024.08.12