Home [실전 자바 기본편] final
Post
Cancel

[실전 자바 기본편] final

final

  • 김영한님의 실전 자바 강의 중 final 챕터를 학습하며 final 키워드의 목적, 변경 불가능성의 필요성, 그리고 지역 변수, 필드, 상수에서의 사용법을 정리함



final 키워드의 목적

변경 불가능성이 왜 필요한가

  • 문제 상황

    • 프로그램에서 특정 값이 변경되면 버그가 발생하는 경우가 많음
    • 개발자의 실수로 중요한 값이 변경될 수 있음
    • 여러 곳에서 공유하는 값이 예기치 않게 변경될 수 있음
  • 해결책

    • 값의 변경을 원천적으로 막아야 함
    • 컴파일 시점에 변경을 감지하여 오류를 발생시켜야 함
    • 코드의 의도를 명확히 전달해야 함

final 키워드란

  • final 키워드는 변경 불가능을 의미함
  • final이 붙은 변수는 최초 한 번만 값을 할당할 수 있음
  • 이후 값을 변경하려고 하면 컴파일 오류가 발생함

    1
    2
    
    final int value = 10;
    // value = 20;  // 컴파일 오류 발생
    

final의 적용 대상

  • 변수

    • 지역 변수와 매개변수: 값 재할당 방지
    • 인스턴스 필드: 객체별 불변 값 생성
    • 클래스(static) 필드: 전역 상수 선언
  • 메서드

    • 오버라이딩을 방지함
  • 클래스

    • 상속을 방지함



final 지역 변수

지역 변수에 final 적용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package final1;

public class FinalLocalMain {
    public static void main(String[] args) {
        // final 지역 변수는 선언과 초기화를 분리 가능함
        final int data1;
        data1 = 10; // 최초 한 번만 할당 가능함
        // data1 = 20; // 컴파일 오류 발생

        // final 지역 변수는 선언과 동시에 초기화 가능함
        final int data2 = 10;
        // data2 = 20; // 컴파일 오류 발생

        method(10);
    }

    // final 매개변수
    static void method(final int parameter) {
        // parameter = 20; // 컴파일 오류 발생
    }
}
  • 규칙
    • final 변수는 최초 한 번만 할당 가능함
    • 선언과 초기화를 분리할 수 있지만, 최초 할당 이후 값 변경 불가함
    • final 매개변수는 메서드 내에서 값을 변경할 수 없음



final 필드

생성자를 통한 초기화

1
2
3
4
5
6
7
8
9
package final1;

public class Settings {
    final int threshold;

    public Settings(int threshold) {
        this.threshold = threshold;
    }
}
  • 생성자에서 final 필드를 초기화함
  • 각 인스턴스마다 서로 다른 final 값을 가질 수 있음

필드 선언 시 초기화

1
2
3
4
5
6
package final1;

public class SharedSettings {
    final int limit = 10;              // 인스턴스 필드
    static final int MAX_LIMIT = 10;   // 클래스 필드
}
  • 인스턴스 final 필드 (final int limit)

    • 모든 인스턴스가 동일한 값 10을 가짐
    • 각 인스턴스마다 별도의 메모리 공간을 차지함
    • 객체가 100개면 동일한 값 10이 100번 저장됨 (메모리 낭비)
  • 클래스 final 필드 (static final int MAX_LIMIT)

    • 메서드 영역(static 영역)에 단 1개만 존재함
    • 모든 인스턴스가 이 하나의 값을 공유함
    • 메모리 효율적이며, 전역 상수로 사용하기 적합함

메모리 비교(객체 1000개 생성 시)

  • final int limit = 10은 4KB 사용 (4byte × 1000)
  • static final int MAX_LIMIT = 10은 4byte만 사용

초기화 방식 비교

초기화 방식 메모리 위치 특징 사용 시기
생성자 초기화 힙 (각 인스턴스) 객체마다 다른 값 가능 객체별로 다른 final 값 필요 시
필드 초기화 힙 (각 인스턴스) 모든 객체가 같은 값 공유 비효율적, static 사용 권장
static final 메서드 영역 (단 1개) 전역 공유 상수 정의에 적합
  • 생성자 초기화 - 객체별 다른 값

    1
    2
    3
    4
    
    Settings s1 = new Settings(10);
    Settings s2 = new Settings(20);
    System.out.println(s1.threshold); // 10
    System.out.println(s2.threshold); // 20
    
    • 사용 사례
      • 주문 번호, 회원 ID처럼 객체마다 달라야 하는 불변 값
  • 필드 초기화 - 모든 객체가 같은 값

    1
    2
    3
    4
    
    SharedSettings s1 = new SharedSettings();
    SharedSettings s2 = new SharedSettings();
    System.out.println(s1.limit); // 10
    System.out.println(s2.limit); // 10 (같은 값인데 메모리는 따로)
    
    • 문제점
      • 모든 객체가 같은 값인데 각 객체마다 메모리를 차지함
    • 해결책
      • static final을 사용함
  • static final - 상수

    1
    
    System.out.println(SharedSettings.MAX_LIMIT); // 10 (객체 생성 없이 접근)
    

메모리 구조 비교

Memory Comparison

  • Settings
    • 인스턴스별로 다른 final 값을 가질 수 있어 의미가 있음
  • SharedSettings
    • 모든 인스턴스가 같은 값(10)을 가지면서 메모리를 중복해서 사용함
  • static final
    • 메서드 영역에 하나만 존재하므로 가장 효율적임

static final로 상수 정의

  • 상수란

    • 프로그램에서 절대 변하지 않는 공용 값을 의미함
    • 자바에서는 static final 키워드를 사용하여 정의함
  • 상수 명명 관례

    • 대문자로 작성하고, 단어 사이는 _ (언더스코어)로 구분함
    • 일반 변수(소문자 시작)와 명확히 구분하기 위함
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    package final1;
    
    public class GameConfig {
        // 게임 설정 상수
        public static final int MAX_PLAYERS = 4;
    
        // 수학 상수
        public static final double PI = 3.14;
    }
    

상수를 사용해야 하는 이유

  • 문제점

    • 가독성 저하
      • 코드에 있는 숫자가 정확히 어떤 용도인지 파악하기 힘듦
    • 유지보수 어려움
      • 해당 숫자가 코드 여러 곳에 퍼져 있다면, 값을 변경할 때 모든 곳을 찾아서 수정해야 함
      • 하나라도 빠뜨리면 버그가 발생할 수 있음
  • 해결책

    • 상수를 사용하여 숫자에 의미 있는 이름을 부여하고 한 곳에서 관리해야 함

상수 사용 전후 비교

  • 상수 사용 전

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    package final1;
    
    public class GameMain {
        public static void main(String[] args) {
            System.out.println("최대 플레이어 수: " + 4);
            int currentPlayerCount = 3;
            joinGame(currentPlayerCount++);
            joinGame(currentPlayerCount++);
            joinGame(currentPlayerCount++);
        }
    
        private static void joinGame(int currentPlayerCount) {
            System.out.println("참가자 수: " + currentPlayerCount);
            if (currentPlayerCount > 4) {
                System.out.println("대기열로 이동합니다.");
            } else {
                System.out.println("게임에 참가합니다.");
            }
        }
    }
    
  • 상수 사용 후

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    package final1;
    
    public class GameMainRefactored {
        public static void main(String[] args) {
            System.out.println("최대 플레이어 수: " + GameConfig.MAX_PLAYERS);
            int currentPlayerCount = 3;
            joinGame(currentPlayerCount++);
            joinGame(currentPlayerCount++);
            joinGame(currentPlayerCount++);
        }
    
        private static void joinGame(int currentPlayerCount) {
            System.out.println("참가자 수: " + currentPlayerCount);
            if (currentPlayerCount > GameConfig.MAX_PLAYERS) {
                System.out.println("대기열로 이동합니다.");
            } else {
                System.out.println("게임에 참가합니다.");
            }
        }
    }
    
  • 가독성과 유지보수성이 모두 향상됨



final 변수와 참조

참조형 변수에 final 적용

1
2
3
4
5
package final1;

public class Data {
    public int value;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package final1;

public class FinalRefMain {
    public static void main(String[] args) {
        final Data data = new Data();
        // data = new Data(); // final 변수이므로 참조 대상 변경 불가

        // 참조 대상의 값은 변경 가능함
        data.value = 10;
        System.out.println(data.value); // 10
        data.value = 20;
        System.out.println(data.value); // 20
    }
}

메모리 구조와 final 참조

Final Reference Memory

  • 가능 (data.value = 20)

    • 참조하는 대상의 객체 내부 값(value)은 변경할 수 있음
    • final은 참조 변수 data의 값을 고정하는 것이지, 힙 영역에 있는 객체 자체를 불변으로 만드는 것이 아님
  • 불가 (data = new Data())

    • 참조값(주소) 자체를 변경할 수 없음
    • 한 번 가리킨 객체(x001)를 끝까지 가리켜야 함



final 사용 가이드

언제 사용하는가

  • 값이 변경되면 안 되는 경우
  • 중앙에서 값을 관리하고 싶은 경우
  • 코드의 가독성과 안정성을 높이고 싶은 경우

장점

  • 코드 안정성 증가 (실수로 값 변경 방지)
  • 의도가 명확해짐 (이 값은 변경되지 않는다는 의미)
  • 유지보수성 향상 (상수를 한 곳에서 관리)

주의사항

  • final은 변수 자체의 재할당을 막는 것임
  • 참조형의 경우 참조 대상 객체의 내부 값은 변경 가능함
  • 완전한 불변 객체를 만들려면 모든 필드를 final로 선언하고 setter를 제공하지 않아야 함



연습 문제

  1. final 키워드를 변수에 사용하면 어떤 것이 제한될까요?

    • a. 값의 변경

    • final은 변수에 값이 한 번 할당되면 그 값을 다시 변경할 수 없도록 제한하는 키워드임

  2. 참조 변수에 final 키워드를 붙이면 무엇이 고정될까요?

    • a. 참조 변수가 가리키는 주소값

    • 참조 변수에 final을 쓰면 변수가 가리키는 주소, 즉 다른 객체를 참조하도록 바꿀 수 없게 됨
    • 객체 자체의 속성 값은 변경 가능함
  3. Java에서 static final로 선언된 변수를 무엇이라고 부를까요?

    • a. 상수

    • static은 공유, final은 변경 불가 속성임
    • 이 둘을 함께 써서 프로그램 내내 바뀌지 않고 공유되는 ‘상수’를 정의함
  4. MAX_USERS처럼 프로그램 전반에 걸쳐 사용되는 고정된 값에 static final을 사용하는 주된 이유는 무엇일까요?

    • a. 값의 중앙 집중식 관리와 가독성 향상

    • static final 상수는 값을 한 곳에서 관리하여 코드 이해를 돕고, 필요시 값 변경을 중앙에서 쉽게 할 수 있게 해줌

  5. 변수에 final 키워드를 사용했을 때 얻을 수 있는 중요한 이점은 무엇일까요?

    • a. 개발자의 실수를 통한 값 변경 방지

    • final은 해당 변수의 값이 절대 바뀌지 않음을 보장하여, 개발자가 의도치 않게 값을 변경하는 실수를 컴파일 단계에서 미리 막아주는 이점이 있음



요약 정리

final의 목적

  • 변경 불가능성을 통한 안정성 확보
  • 개발자 실수 방지
  • 코드 의도 명확화

final 사용 목적별 분류

분류 키워드 적용 위치 설명
재할당 방지 기본형 변수 값을 한 번만 할당 가능, 이후 변경 불가
참조 고정 참조형 변수 참조하는 객체(주소)를 변경 불가, 객체 내부 상태는 변경 가능
불변 필드 인스턴스 필드 생성자 초기화 이후 값 변경 불가 (개별 객체마다 다른 값 가능)
상수 static 필드 static final, 전역적으로 공유되는 불변의 값
확장과 변경 금지 메서드와 클래스 오버라이딩 불가(메서드), 상속 불가(클래스)



Reference

Contents