Java - Final 키워드에 대한 이해

JS · 04 Jul 2020

자바에서 클래스나 변수를 정의할 때 final 키워드를 사용할 수 있습니다. 클래스나 변수에 final을 붙이면 시간이 지나도 처음 정의된 상태가 변하지 않는 것을 보장한다는 의미입니다.

다음과 같은 용도로 사용됩니다.

  • final 변수
  • final 인자
  • final 클래스
  • final 메소드

각각 어떻게 final을 선언하고 정의하는지 알아보겠습니다.

1. final variables (변수)

다음과 같이 변수를 선언할 때, 클래스 앞에 final 키워드를 입력할 수 있습니다.

final String hello = "Hello world";

final 키워드가 붙은 변수는 초기화 후 변경할 수 없습니다. 다음과 같이 변경하려고 하면 컴파일 에러가 발생합니다.

final String hello = "Hello world";

hello = "See you around" // compile error!

if 문에서 초기화

변수를 선언할 때 꼭 초기화가 필요한 것은 아닙니다. 다음과 같이 if문을 사용하여 조건별로 초기화를 다르게 할 수 있습니다. 물론, 한번 초기화되면 다른 값으로 설정할 수 없습니다.

final String hello;

boolean condition = true;
if (condition) {
     hello = "Hello world";
} else {
     hello = "See you around";
}

클래스 생성자에서 초기화

클래스의 멤버 변수에 final을 사용할 때는 클래스의 생성자에서 초기화할 수 있습니다.

class AAA {
    final String hello;
    AAA() {
        hello = "hello world";
    }
}

static final 변수 초기화

static 변수도 다음과 같이 final 변수로 만들고 초기화할 수 있습니다. static final 변수도 일반적인 final 변수와 동일한 특징을 갖고 있습니다.

static final String hello = "hello world";

static final 변수를 선언할 때 초기화하지 싶지 않다면, 다음과 같이 static { } 안에서 초기화할 수 있습니다.

static final String hello;

static {
    hello = "hello world";
}

2. final arguments (인자)

인자를 선언할 때 final 키워드를 사용할 수 있습니다. final로 선언된 인자는 메소드 내에서 변경이 불가능합니다.

따라서 다음과 같이 final int로 선언한 number는 읽을 수 있지만 number = 10처럼 값을 변경하려고 하면 컴파일 에러가 발생합니다.

public void func(final int number) {
    System.out.println(number);

    // number = 10;  compile error!
}

3. final 클래스

클래스를 정의할 때 다음과 같이 final 키워드를 사용할 수 있습니다.

final class AAA {
    final String hello;
    AAA() {
        hello = "hello world";
    }
}

변수가 아닌 클래스에 final을 붙이는 것은 어떤 의미일까요?

클래스에 final을 붙이면 다른 클래스가 상속할 수 없는 클래스가 됩니다. 만약 다음과 같이 final 클래스를 상속하려고 하면 컴파일 에러가 발생합니다.

final class AAA {
    final String hello;
    AAA() {
        hello = "hello world";
    }
}

class BBB extends AAA() { // compile error!
}

4. final 메소드

다음과 같이 클래스의 메소드에도 final keyword를 붙일 수 있습니다.

class AAA {
    final String hello = "hello world";

    final String getHello() {
        return hello;
    }
}

final 메소드는 Override가 안되도록 만듭니다.

예를 들면, 다음과 같이 AAA 클래스를 상속하는 BBB 클래스에서는 getHello()를 재정의할 수 없습니다. Override하려고 하면 컴파일 에러가 발생합니다.

class BBB extends AAA {

    @Override
    String getHello() { // compile error !
        return "See you around";
    }
}

정리

지금까지 살펴본 내용들을 정리하면 final 키워드는 다음과 같이 상태 변경이 안되도록 합니다.

  • final variables, arguments : 값이 변경되지 않도록 만듬
  • final class : 클래스를 상속하지 못하도록 만듬
  • final method : 메소드가 오버라이드되지 못하도록 만듬

주의할 점

final 변수는 초기화 이후 값 변경이 발생하지 않도록 만듭니다.

다음 코드를 보시면 List에 final을 선언하여 list 변수의 변경은 불가능합니다. 하지만 list 내부에 있는 변수들은 변경이 가능하여 문자열을 계속 추가할 수 있습니다.

final List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");

따라서, final 변수의 변경을 막아주지만 final 변수 내부에 갖고 있는 변수들은 변경이 가능하다는 점을 기억하시면 좋을 것 같습니다.

Effective final keyword

Effective final은 Java8에서 추가된 기능입니다.

final이 붙지 않은 변수의 값이 변경되지 않는다면, 그 변수를 Effectively final이라고 합니다. 즉, final을 붙이지 않았지만 컴파일러가 final로 취급하는 것입니다.

예를 들면, 다음 코드에서 변수 num은 Effectively final입니다. num은 선언과 동시에 10으로 할당되었고 객체가 소멸될 때까지 값이 변경되지 않았기 때문입니다.

int num = 10;
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("number: " + num);
    }
};
runnable.run();

Java8 이전에는 Effective final이 없었기 때문에 위의 코드는 컴파일 에러가 발생합니다. run()안에서 변경이 가능한 num 변수에 접근하기 때문입니다. 하지만 Java8은 num 변수가 내부에서 변경되지 않기 때문에 final로 취급하여 컴파일 에러가 발생하지 않습니다.

더 자세한 내용은 Java의 Effectively final이란 무엇인가?를 참고하시면 좋습니다.

댓글을 보거나 쓰려면 이 버튼을 눌러주세요.
codechachaCopyright ©2019 codechacha