[Java] final

szlee·2023년 12월 10일
0

Java

목록 보기
17/23

< 김영한의 실전 자바 - 기본편 > 강의를 보고 이해한 내용을 바탕으로 합니다.





final 변수와 상수

final키워드는 이름 그대로 끝이라는 뜻이다.
변수에 final 키워드가 붙으면 더는 값을 변경할 수 없다.

참고로 finalclass, method를 포함한 여러 곳에 붙을 수 있다.


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

        //final 지역변수2
        final int data2 = 10;
        //data2 = 20; // compile error
    }

    static void method(final int param){
        //param = 20; // compile error
    }
}
  • final을 지역변수에 설정할 경우 최초 한번만 할당할 수 있다. 이후에 변수의 값을 변경하려면 컴파일 오류가 발생한다.
  • final을 지역변수 선언 시 바로 초기화한 경우 이미 값이 할당되었기 때문에 값을 할당할 수 없다.
  • 매개변수에 final이 붙으면 메서드 내부에서 매개변수의 값을 변경할 수 없다. 따라서 메서드 호출 시점에 사용된 값이 끝까지 사용된다.

final 멤버변수

public class ConstructInit {
    final int value;

    public ConstructInit(int value) {
        this.value = value;
    }
}
public class FieldInit {
    static final int CONST_VALUE = 10; //상수 대문자 (관례)
    final int value = 10;
}
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);
    }
}

실행 결과

생성자 초기화
10
20
필드 초기화
10
10
10
상수
10

ConstructInit과 같이 생성자를 사용해서 final필드를 초기화 하는 경우, 각 인스턴스마다 final필드에 다른 값을 할당할 수 있다. 물론 final을 사용했기 때문에 생성 이후에 이 값을 변경하는 것은 불가능하다.

  • FieldInit과 같이 final필드를 필드에서 초기화 하는 경우, 모든 인스턴스가 같은 값을 가진다.
    • FieldInit 인스턴스의 모든 value값은 10이 된다.
  • 생성자 초기화와 다르게 필드 초기화는 필드의 코드에 해당 값이 미리 정해져있기 때문이다.
  • 모든 인스턴스가 같은 값을 사용하기 때문에 결과적으로 메모리를 낭비하게 된다. 또한 메모리 낭비를 떠나서 같은 값이 계속 생성되는 것은 개발자가 보기에 명확하게 중복이므로, 이럴 때 static영역을 사용하면 좋다.

static final

  • FieldInit.CONST_VALUEstatic영역에 존재한다. 그리고 final키워드를 사용해서 초기화 값이 변하지 않는다.
  • static 영역은 단 하나만 존재하는 영역이다. CONST_VALUE변수는 JVM상에서 하나만 존재하기 때문에 앞선 중복과 메모리 비효율 문제를 모두 해결할 수 있다.

이런 이유로 필드에 final + 필드 초기화를 사용하는 경우 static을 붙여서 사용하는 것이 효과적이다.



상수

변하지 않고 항상 일정한 값을 갖는 수. 자바에서는 보통 단 하나만 존재하는 변하지 않는 고정된 값을 상수라 한다.
이런 이유로 상수는 static final 키워드를 사용한다.

자바 상수 특징

  • static final 키워드를 사용한다.
  • 대문자를 사용하고 구분은 _로 한다.(관례)
    • 일반적인 변수와 상수를 구분하기 위해 이렇게 한다.
  • 필드에 직접 접근해서 사용한다.
    • 상수는 기능이 아니라 고정된 값 자체를 사용하는 것이 목적이다.
    • 상수는 값을 변경할 수 없다. 따라서 필드에 직접 접근해도 데이터가 변하는 문제가 발생하지 않는다.
//상수
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을 자주 사용한다. 물론 특정 위치에서만 사용된다면 다른 접근 제어자를 사용하면 된다.
  • 상수는 중앙에서 값을 하나로 관리할 수 있다는 장점도 있다.
  • 상수는 런타임에 변경할 수 없다. 상수를 변경하려면 프로그램을 종료하고, 코드를 변경한 다음에 프로그램을 다시 실행해야 한다.
  • 상수는 중앙에서 값을 하나로 관리할 수 있다는 장점도 있다.
public class ConstantMain2 {
    public static void main(String[] args) {
        System.out.println("프로그램 최대 참여자 수 " + Constant.MAX_USERS);
        int currentUserCount = 999;
        process(currentUserCount++);
        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 상수를 사용했다. 만약 프로그램 최대 참여자 수를 변경해야하면 Constant.MAX_USERS 값만 변경하면 된다.
  • 매직넘버 문제를 해결했다. 숫자 1000이 아니라 사람이 인지할 수 있게 MAX_USERS라는 변수명으로 코드를 이해할 수 있다.



final 변수와 참조

final은 변수의 값을 변경하지 못하게 막는다. 여기서 변수의 값이라는 것은?

  • 변수는 크게 기본형 변수와 참조형 변수가 있다.
  • 기본형 변수는 10, 20 같은 값을 보관하고, 참조현 변수는 객체의 참조값을 보관한다.
    • final을 기본형 변수에 사용하면 값을 변경할 수 없다.
    • final을 참조형 변수에 사용하면 참조값을 변경할 수 없다.
public class FinalRefMain {
    public static void main(String[] args) {
        final Data data = new Data();; //참조형
        //data = new Data();

        //침조 대상의 값은 변경 가능
        data.value = 10;
        System.out.println(data.value);
        data.value = 20;
        System.out.println(data.value);
    }
}
final Data data = new Data();; //참조형
        //data = new Data(); //변경 불가. compile error

참조형 변수 datafinal이 붙었다. 변수 선언 시점에 참조값을 할당했으므로 더는 참조값을 변경할 수 없다.

data.value = 10
data.value = 20

하지만 참조 대상의 객체 값은 변경할 수 있다.

  • 참조형 변수 datafinal이 붙었다. 이 경우 참조형 변수에 들어있는 참조값을 다른 값으로 변경하지 못한다. 참조형 변수에 들어있는 참조값만 변경하지 못한다는 뜻이다.
  • Data.valuefinal이 아니다. 따라서 값을 변경할 수 있다.

참조형 변수에 final이 붙으면 참조 대상 자체를 다른 대상으로 변경하지 못한다는 것이지 참조하는 대상의 값은 변경할 수 있다.

profile
🌱

0개의 댓글