[Java] 개념노트 25 final 키워드 이해하기

[Java 개념노트 25] final 키워드 이해하기

안녕하세요. Java 개념노트 시리즈 스물다섯 번째 글입니다.

지난 글에서는 클래스가 함께 공유하는 값과 기능을 만들 때 사용하는 static 키워드에 대해 정리했습니다. 이번 글에서는 값을 변경하지 못하게 막을 때 사용하는 final 키워드에 대해 알아보겠습니다.

final은 자바에서 중요한 키워드입니다. 변수에 붙이면 값을 다시 바꿀 수 없고, 메서드에 붙이면 오버라이딩을 막을 수 있고, 클래스에 붙이면 상속을 막을 수 있습니다. 처음에는 “변경 금지”라는 큰 의미로 이해하면 좋습니다.


1. final이란?

final은 한 번 정해진 값을 다시 변경하지 못하게 막는 키워드입니다.

변수, 필드, 메서드, 클래스에 사용할 수 있으며 어디에 붙느냐에 따라 의미가 조금씩 달라집니다.

쉽게 말하면
final은 “이제 더 이상 바꾸지 마라”라는 의미를 가진 키워드입니다.

2. final을 사용할 수 있는 곳

final은 여러 위치에 사용할 수 있습니다.

사용 위치 의미 예시
변수 값을 다시 대입할 수 없습니다. final int number = 10;
필드 객체의 필드 값을 한 번만 정할 수 있습니다. final String name;
메서드 자식 클래스에서 오버라이딩할 수 없습니다. final void print()
클래스 다른 클래스가 상속할 수 없습니다. final class Member

3. final 변수

변수에 final을 붙이면 한 번 값을 저장한 뒤 다시 변경할 수 없습니다.

public class Main {
    public static void main(String[] args) {
        final int number = 10;

        System.out.println(number);
    }
}

실행 결과는 다음과 같습니다.

10

number 변수는 final로 선언되었기 때문에 이후에 다른 값을 다시 넣을 수 없습니다.

4. final 변수 값 변경 시도

final 변수에 값을 다시 대입하려고 하면 오류가 발생합니다.

public class Main {
    public static void main(String[] args) {
        final int number = 10;

        number = 20; // 오류 발생
    }
}

number는 이미 10으로 초기화되었습니다. 따라서 20을 다시 저장할 수 없습니다.

주의
final 변수는 한 번 값이 정해지면 다시 대입할 수 없습니다.

5. final 변수는 반드시 초기화해야 한다

final 변수는 사용하기 전에 반드시 값이 정해져야 합니다. 선언과 동시에 초기화할 수도 있고, 나중에 한 번만 값을 넣을 수도 있습니다.

선언과 동시에 초기화

final int number = 10;

나중에 한 번 초기화

final int number;

number = 10;

단, 한 번 값이 들어간 뒤에는 다시 값을 변경할 수 없습니다.

6. final과 상수

상수는 한 번 정하면 바뀌지 않는 값입니다. 예를 들어 원주율, 최대 로그인 시도 횟수, 기본 할인율처럼 프로그램 전체에서 고정적으로 사용하는 값을 상수로 만들 수 있습니다.

final double PI = 3.141592;
final int MAX_LOGIN_COUNT = 5;

상수 이름은 보통 대문자와 밑줄을 사용해서 작성합니다.

일반 변수 상수
int count final int MAX_COUNT
double rate final double DISCOUNT_RATE

7. static final 상수

상수를 만들 때는 static final 조합을 자주 사용합니다.

class AppConfig {
    static final String APP_NAME = "GWDEVELBlog";
    static final int MAX_USER_COUNT = 100;
}

static은 클래스가 공유한다는 의미이고, final은 값을 바꿀 수 없다는 의미입니다. 따라서 static final은 클래스 전체에서 공유하는 변경 불가능한 값이라고 볼 수 있습니다.

System.out.println(AppConfig.APP_NAME);
System.out.println(AppConfig.MAX_USER_COUNT);

핵심 포인트
프로그램 전체에서 함께 사용하는 고정값은 static final 상수로 만드는 경우가 많습니다.

8. final 필드

클래스의 필드에도 final을 붙일 수 있습니다. final 필드는 객체가 생성될 때 한 번 값이 정해지면 이후 변경할 수 없습니다.

class Member {
    final String id;

    Member(String id) {
        this.id = id;
    }
}

id는 회원의 고유 아이디라고 가정할 수 있습니다. 회원 객체가 만들어질 때 아이디가 정해지고, 이후에는 바뀌면 안 되므로 final을 붙일 수 있습니다.

9. final 필드는 생성자에서 초기화할 수 있다

final 필드는 선언할 때 바로 초기화하거나, 생성자에서 초기화할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        Member member = new Member("user01");

        System.out.println(member.id);
    }
}

class Member {
    final String id;

    Member(String id) {
        this.id = id;
    }
}

실행 결과는 다음과 같습니다.

user01

id 필드는 생성자에서 한 번 초기화됩니다. 이후에는 다른 값으로 바꿀 수 없습니다.

10. final 필드 변경 시도

final 필드를 생성 후 다시 바꾸려고 하면 오류가 발생합니다.

Member member = new Member("user01");

member.id = "user02"; // 오류 발생

id는 final 필드이므로 이미 값이 정해진 뒤에는 다시 대입할 수 없습니다.

11. final 참조 변수

객체를 가리키는 참조 변수에도 final을 붙일 수 있습니다. 이 경우 참조 변수는 다른 객체를 다시 가리킬 수 없습니다.

final Member member = new Member("user01");

member = new Member("user02"); // 오류 발생

member 참조 변수는 처음 생성한 객체만 가리킬 수 있습니다. 다른 객체를 새로 대입할 수 없습니다.

12. final 참조 변수의 객체 내부 값은 바꿀 수 있을까?

여기서 중요한 점이 있습니다. final 참조 변수는 다른 객체를 다시 가리킬 수 없다는 뜻이지, 객체 내부의 모든 값이 무조건 바뀌지 않는다는 뜻은 아닙니다.

class Product {
    String name;
    int price;

    Product(String name, int price) {
        this.name = name;
        this.price = price;
    }
}

public class Main {
    public static void main(String[] args) {
        final Product product = new Product("키보드", 50000);

        product.price = 45000;

        System.out.println(product.price);
    }
}

실행 결과는 다음과 같습니다.

45000

product 변수는 final이므로 다른 객체를 다시 가리킬 수 없습니다. 하지만 Product 객체의 price 필드는 final이 아니므로 값을 변경할 수 있습니다.

주의
참조 변수에 final을 붙이면 참조 대상 변경이 막히는 것이지, 객체 내부 값 변경이 항상 막히는 것은 아닙니다.

13. final 배열

배열도 참조 자료형입니다. 따라서 final 배열은 다른 배열로 다시 대입할 수 없지만, 배열 안의 요소는 변경할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        final int[] numbers = {1, 2, 3};

        numbers[0] = 100;

        System.out.println(numbers[0]);
    }
}

실행 결과는 다음과 같습니다.

100

배열 변수 numbers는 다른 배열을 가리킬 수 없지만, numbers[0] 값은 변경할 수 있습니다.

final int[] numbers = {1, 2, 3};

numbers = new int[]{4, 5, 6}; // 오류 발생

14. final 매개변수

메서드의 매개변수에도 final을 붙일 수 있습니다. final 매개변수는 메서드 안에서 다시 값을 대입할 수 없습니다.

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

    // number = 20; // 오류 발생
}

이 방식은 매개변수 값이 메서드 안에서 실수로 바뀌는 것을 막고 싶을 때 사용할 수 있습니다. 다만 모든 매개변수에 무조건 붙이기보다는 필요한 경우에 사용하는 편이 좋습니다.

15. final 메서드

메서드에 final을 붙이면 자식 클래스에서 해당 메서드를 오버라이딩할 수 없습니다.

class Parent {
    final void printMessage() {
        System.out.println("변경할 수 없는 메서드입니다.");
    }
}

상속과 오버라이딩은 뒤에서 더 자세히 다룰 예정입니다. 지금은 final 메서드는 자식 클래스에서 재정의할 수 없다 정도로 이해하면 됩니다.

16. final 클래스

클래스에 final을 붙이면 다른 클래스가 이 클래스를 상속할 수 없습니다.

final class Member {
    String id;
}

final class는 더 이상 확장되지 않도록 막고 싶을 때 사용합니다. 대표적으로 자바의 String 클래스도 final 클래스입니다.

미리 보기
상속을 배운 뒤에는 final classfinal method의 의미가 더 명확해집니다. 이번 글에서는 “상속과 재정의를 막는다” 정도로 기억하면 충분합니다.

17. final과 static final 차이

finalstatic final은 비슷해 보이지만 용도가 다릅니다.

구분 의미 예시
final 한 번 정해진 값을 변경할 수 없음 객체마다 다른 고유 id
static final 클래스가 공유하는 변경 불가능한 값 상수, 설정값, 고정 메시지
class Member {
    final String id;
    static final String SERVICE_NAME = "GWDEVELBlog";

    Member(String id) {
        this.id = id;
    }
}

id는 객체마다 다를 수 있지만 한 번 정해지면 바뀌면 안 됩니다. 반면 SERVICE_NAME은 모든 객체가 공유하는 고정값입니다.

18. final 전체 예제

이번에는 finalstatic final을 함께 사용하는 예제를 보겠습니다.

public class Main {
    public static void main(String[] args) {
        User user = new User("user01", "건");

        user.printInfo();

        System.out.println(User.SERVICE_NAME);
    }
}

class User {
    static final String SERVICE_NAME = "GWDEVELBlog";

    final String id;
    String name;

    User(String id, String name) {
        this.id = id;
        this.name = name;
    }

    void printInfo() {
        System.out.println("아이디: " + id);
        System.out.println("이름: " + name);
    }
}

실행 결과는 다음과 같습니다.

아이디: user01
이름: 건
GWDEVELBlog

id는 사용자마다 다른 고유값이므로 final 필드로 만들었습니다. SERVICE_NAME은 모든 사용자가 공유하는 고정값이므로 static final 상수로 만들었습니다.

19. final 사용 시 자주 하는 실수

final을 처음 배울 때는 아래와 같은 실수를 자주 합니다.

실수 문제점 해결 방법
final 변수에 값을 다시 대입 컴파일 오류 발생 final 값은 한 번만 대입합니다.
final 필드를 초기화하지 않음 객체 생성 시 값이 정해지지 않음 선언 시 또는 생성자에서 초기화합니다.
final 참조 변수면 객체 내부도 전부 불변이라고 생각 참조 변경과 내부 값 변경을 혼동 final 참조는 재대입만 막는다고 이해합니다.
상수를 소문자로 작성 상수임을 알아보기 어려움 MAX_COUNT처럼 대문자와 밑줄 사용
모든 곳에 무조건 final 사용 코드 작성이 불필요하게 복잡해질 수 있음 변경되면 안 되는 값에 명확히 사용합니다.

20. 직접 연습해보기

아래 코드를 직접 작성하고 실행해보세요.

public class Main {
    public static void main(String[] args) {
        Product product = new Product("P001", "키보드", 50000);

        product.printInfo();

        product.name = "기계식 키보드";
        product.price = 45000;

        product.printInfo();

        System.out.println(Product.STORE_NAME);
        System.out.println(Product.MAX_DISCOUNT_RATE);
    }
}

class Product {
    static final String STORE_NAME = "GWDEVEL Store";
    static final double MAX_DISCOUNT_RATE = 0.3;

    final String productCode;
    String name;
    int price;

    Product(String productCode, String name, int price) {
        this.productCode = productCode;
        this.name = name;
        this.price = price;
    }

    void printInfo() {
        System.out.println("상품코드: " + productCode);
        System.out.println("상품명: " + name);
        System.out.println("가격: " + price);
    }
}

실행 결과는 다음과 같습니다.

상품코드: P001
상품명: 키보드
가격: 50000
상품코드: P001
상품명: 기계식 키보드
가격: 45000
GWDEVEL Store
0.3

productCode는 final 필드이므로 한 번 정해지면 바뀌지 않습니다. 반면 nameprice는 final이 아니므로 수정할 수 있습니다.

21. 이번 글 정리

이번 글에서는 값을 변경하지 못하게 막는 final 키워드에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.

  • final은 변경을 막는 키워드이다.
  • final 변수는 한 번 값이 정해지면 다시 대입할 수 없다.
  • final 필드는 선언 시 또는 생성자에서 초기화해야 한다.
  • 상수는 보통 static final로 만든다.
  • 상수 이름은 보통 대문자와 밑줄로 작성한다.
  • final 참조 변수는 다른 객체로 재대입할 수 없다.
  • final 참조 변수라도 객체 내부 필드가 final이 아니면 값 변경이 가능하다.
  • final 배열은 다른 배열로 재대입할 수 없지만 배열 요소는 변경할 수 있다.
  • final 메서드는 오버라이딩할 수 없다.
  • final 클래스는 상속할 수 없다.

한 줄 요약
final은 변수, 필드, 메서드, 클래스에 붙어 변경이나 확장을 제한하는 키워드입니다.

다음 글 예고
다음 글에서는 [Java 개념노트 26] 접근 제어자 이해하기라는 주제로 public, private, protected, default 접근 범위를 정리해보겠습니다.

GWDEVELBlog Java 개념노트 시리즈