본문 바로가기
IT/김영한의실전JAVA-기본_inFlearn

8. final - final 변수와 상수1~2 / final 변수와 참조 / 정리

by for-learn 2025. 2. 19.

8-1. final 변수와 상수1

FinalLocalMain.java (지역변수)

package final1;

public class FinalLocalMain {
    public static void main(String[] args) {
        // final 지역 변수1
        final int data1;
        data1 = 10; // 최초 한번만 할당 가능
        //data1 = 20; // 컴파일 오류

        // final 지역 변수2
        final int data2 = 10;
        //data2 = 20; // 컴파일 오류

        method(10);
    }

    // final 매개변수
    static void method(final int parameter) {
        //parameter = 20; //컴파일 오류 => 매개변수로 넘어오는 값을 변경 못한다.
    }
}
  • final을 지역 변수에 설정한 경우 최초 한번만 할당 => 이후 변수의 값 변경하면 컴파일 오류
  • 매개변수에 final 이 붙으면 메서드 내부에서 매개변수의 값 변경 불가

ConstructInit.java(멤버 변수/필드 - 생성자 초기화)

package final1;

// final 필드 - 생성자 초기화
public class ConstructInit {
    final int value;

    public ConstructInit(int value) {
        this.value = value;
    }
}

FieldInit.java(멤버 변수/필드 - 필드 초기화)

package final1;

// final 필드 - 필드 초기화
public class FieldInit {
    static final int CONST_VALUE = 10; // static + final => 변수명 대문자 (관례)
    final int value = 10;
}

FinalFieldMain.java

package final1;

public class FinalFieldMain {
    public static void main(String[] args) {
        // final 필드 - 생성자 초기화
        System.out.println("생성자 초기화");
        ConstructInit constructInit1 = new ConstructInit(10);
        ConstructInit constructInit2 = new ConstructInit(20);
        System.out.println(constructInit1.value);
        System.out.println(constructInit2.value);

        // final 필드 - 필드 초기화
        System.out.println("필드 초기화");
        FieldInit fieldInit1 = new FieldInit();
        FieldInit fieldInit2 = new FieldInit();
        FieldInit fieldInit3 = new FieldInit();
        System.out.println(fieldInit1.value);
        System.out.println(fieldInit2.value);
        System.out.println(fieldInit3.value);

        // 상수
        System.out.println("상수");
        System.out.println(FieldInit.CONST_VALUE);
    }
}

  • 생성자를 사용해 final 필드를 초기화 하는 경우, 각 인스턴스마다 final 필드에 다른 값을 할당 가능
  • final 필드를 필드에서 초기화 하는 경우, 모든 인스턴스가 같은 값을 가짐
  • => 모든 인스턴스가 같은 값을 사용하기 때문에 결과적으로 메모리 낭비 (중복)
  • 이럴 때 static 영역을 사용

static final

  • FieldInit.MY_VALUEstatic 영역에 존재하고, final 키워드를 사용해 초기값이 변하지 않음
  • static 영역은 단 하나만 존재하는 영역이기 때문에, 앞선 중복과 메모리 비효율 문제를 해결 함.
  • 필드에 final + 필드 초기화 하는 경우 static을 붙이는게 효과적

 

8-2. final 변수와 상수2

상수

일반) 변하지 않고, 항상 일정한 값을 갖는 수

자바) 단 하나만 존재하는 변하지 않는 고정된 값 => static final 키워드 사용

 

자바 상수 특징

  • static final 키워드 사용
  • 대문자, 구분 _(언더스코어) 사용 (관례)
    • 일반 변수와 상수 구분
  • 필드에 직접 접근해서 사용
    • 고정된 값 자체를 사용하는 것이 목적
    • 상수 값을 변경할 수 없기에 필드에 직접 접근해도 데이터가 변하는 문제 발생하지 않음

Constant.java

package final1;

public class Constant {
    // 수학 상수
    public static final double PI = 3.14;
    // 시간 상수
    public static final int HOURS_IN_DAY = 24;
    public static final int MINUTES_IN_HOUR = 60;
    public static final int SECONDS_IN_MINUTE = 60;
    // 애플리케이션 설정 상수
    public static final int MAX_USERS = 1000;
}
  • 애플리케이션 안에는 다양한 상수가 존재
    => 수학, 시간 등 실생활에서 사용하는 상수, 애플리케이션의 다양한 설정을 위한 상수들
  • 보통 이런 상수들은 애플리케이션 전반에서 사용되기 때문에 public을 자주 사용.
  • 물론 특정 위치에서만 사용된다면 다른 접근 제어자를 사용하면 됨
  • 상수는 중앙에서 값을 하나로 관리할 수 있다는 장점
  • 상수는 런타임에 변경할 수 없다.
    상수를 변경하려면 프로그램을 종료하고, 코드를 변경한 다음에 프로그램을 다시 실행

ConstantMain1.java (상수 없음)

package final1;

public class ConstantMain1 {
    public static void main(String[] args) {
        System.out.println("프로그램 최대 참여자 수 " + 1000);
        int currentUserCount = 999;
        process(currentUserCount++);
        process(currentUserCount++);
        process(currentUserCount++);
    }

    private static void process(int currentUserCount) {
        System.out.println("참여자 수:" + currentUserCount);
        if (currentUserCount > 1000) {
            System.out.println("대기자로 등록합니다.");
        } else {
            System.out.println("게임에 참여합니다.");
        }
    }
}

ConstantMain2.java (상수 사용)

package final1;

public class ConstantMain2 {
    public static void main(String[] args) {
        System.out.println("프로그램 최대 참여자 수 " + Constant.MAX_USERS);
        int currentUserCount = 999;
        process(currentUserCount++);
        process(currentUserCount++);
        process(currentUserCount++);
    }

    private static void process(int currentUserCount) {
        System.out.println("참여자 수:" + currentUserCount);
        if (currentUserCount > Constant.MAX_USERS) {
            System.out.println("대기자로 등록합니다.");
        } else {
            System.out.println("게임에 참여합니다.");
        }
    }
}

  • 만약 프로그램 최대 참여자 수를 변경해야 하면 Constant.MAX_USERS 의 상수 값만 변경하면 됨
  • 숫자 1000 이 아니라 사람이 인지할 수 있게 MAX_USERS 라는 변수명으로 코드로 이해
    (매직 넘버 문제 해결)

 

8-3. final 변수와 참조

  • final은 변수의 값을 변경하지 못하게 한다. 
  • 변수는 크게 기본형/참조형 변수가 있다.
  • 기본형 변수는 10, 20 같은 값을 보관 => final을 사용하면 값을 변경할 수 없다. 
  • 참조형 변수는 객체의 참조값을 보관 => final을 사용하면 참조값을 변경할 수 없다. 

Data.java

package final1;

public class Data {
    public int value; // 변경할 수 있는 변수
}

FinalRefMain.java

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);
        data.value = 20;
        System.out.println(data.value);
    }
}
  • 참조값을 다른 값으로 변경하지 못하지, 참조 대상의 객체 값은 변경 가능

8-4. 정리

final은 매우 유용한 제약 => ex) 고객의 id 변경 방지

Member.java

package final1.ex;

public class Member {
    private final String id; // final 키워드 사용
    private String name;

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

    public void changeData(String id, String name) {
        //this.id = id; // 컴파일 오류
        this.name = name;
    }

    public void print(){
        System.out.println("id:" + id + ", name:" + name);
    }
}

MemberMain.java

package final1.ex;

public class MemberMain {
    public static void main(String[] args) {
        Member member = new Member("myId", "kim");
        member.print();
        member.changeData("myId2", "seo");
        member.print();
    }
}