final

2024. 2. 7. 18:45Java

◎ final

-> 변수에 final 키워드를 붙이면 값을 더 이상 변경할 수 없다.

public class FinalLocalMain {
    public static void main(String[] args) {
        // final 지역 변수
        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 = 30; // 변경 불가
    }
}

-> 지역변수에 final을 붙이면 최초 한 번만 값을 할당할 수 있다. data1에 final을 붙인 후 처음 10으로 설정한 뒤로는 더 이상 data1에 어떤 값도 할당할 수 없다.

-> 매개변수에 final이 붙은 경우에는 해당 메서드 내부에서 매개변수의 값을 변경할 수 없다, 메서드 호출 시점에 들어간 값이 계속 사용된다.

 

-> 다음은 클래스의 멤버 변수에 final이 붙은 경우다.

public class ConstructInit {
    final int value; // 생성자를 통해서만 값 한 번만 할당

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

 

-> final을 필드에 사용하면 해당 필드는 생성자를 통해서 한 번만 초기화될 수 있다.

public class FieldInit {
    public static final int CONST_VALUE = 10; // 상수
    final int value = 10;

//    public FieldInit() {
//////        this.value = value; // value는 final 이므로 생성자를 통해 값을 넣을 수 없다.
//    }
}

-> FieldInit클래서의 value필드는 final로 선언하여 10으로 초기화했기 때문에 생성자를 통해서 초기화할 수 없다.

-> ConstructInit클래스와 비교해보면 ConstrucInit에서 value는 final로 선언만하고 초기화를 하지 않았기 때문에 생성자를 통해 한 번 초기화를 할 수 있지만 FieldInit의 value는 final로 선언함과 동시에 10으로 초기화를 했기 때문에 더 이상 값을 넣을 수 없다.

-> 또한 static 변수에도 final을 붙일 수 있다.

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);
    }
}

-> 결과

-> ConstructInit처럼 생성자를 사용해 final 필드를 초기화하는 경우 인스턴스를 생성할 때 마다 final 필드에 다른 값을 할당할 수 있다. new 키워드를 사용해 생성된 각 인스턴스들은 각각 다른 위치에 존재하는 인스턴스기 때문이다.

-> FieldInit처럼 final이 붙은 필드를 필드에서 초기화한 경우(final int value = 10) 모든 인스턴스가 위의 그림처럼 같은 값 10을 가진다.

-> FieldInit의 경우 인스턴스를 생성할 때 마다 매번 같은 값이 생성되기 때문에 메모리 낭비가 발생한다. FieldInit 인스턴스가 여러 개 필요한데 value는 매번 같은 값이 생성된다. 공통 값인 value를 메서드 공용 공간인 메서드 영역에 넣어놓고 하나의 value를 가지고 여러 인스턴스가 공유해서 쓰면 해결될 것이다. 이 경우 static을 붙여서 사용한다.

-> Fieldinit 클래스에 final static int CONST_VALUE = 10은 상수로 해당 값을 여러 인스턴스가 공유해서 사용한다. 

-> 인스턴스 생성 없이 클래스로 직접 접근해서 사용하면 된다.

 

◎ 상수

-> 상수는 변하지 않는 일정한 값을 가지는 수다. 자바에서는 보통 단 하나만 존재하는 변하지 않는 고정된 값을 의미한다.

-> 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 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("게임에 참여합니다.");
        }
    }
}

-> 프로그램 최대 참여자 수를 1000으로 설정했다. process 메서드에서는 현재 참여자 수를 파악하여 1000이 넘으면 대기자로 등록하고, 넘지 않으면 게임에 참여하도록 구현했다.

-> 결과

-> 이 코드에는 문제점이 있다. 프로그램 최대 참여자 수가 만약 바뀌면 main 메서드에 있는 1000을 변경하면서 process 메서드에 있는 조건문에도 1000이 아닌 변경된 수로 변경해야 한다. 만약 둘 중 하나만 수정된다면 코드가 원하는대로 동작하지 않을 것이다. 

-> 또한 제한 참여자의 값을 단순히 1000으로 표현하여 이 값이 무엇을 의미하는지 한 번에 이해하기 어렵다.

-> 이 문제를 해결하려면 최대 참여자 수를 상수로 선언하고, 상수의 이름을 통해 이 값이 어떤 값인지를 명시해야 한다.

-> 다음은 상수를 사용한 코드다.

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를 사용했다.

-> 이렇게 상수를 사용하면 이 후에 상수의 값을 변경하려면 Constant 클래스에 MAX_USERS 값만 변경해주면 된다. ConstantMain2 클래스에는 수정할 부분이 없다. 또한 MAX_USERS라는 이름을 사용하여 최대 인원이라는 의미를 유추할 수 있다.

 

◎ final 변수와 참조

-> final은 변수의 값을 변경하지 못하게 한다. 

-> 변수에는 기본형과 참조형 변수가 있다. final을 기본형 변수에 붙이면 값을 변경할 수 없고, 참조형 변수에 붙이면 참조값을 변경할 수 없다.

public class Data {
    public int value;
}

-> Data 클래스의 value는 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);
    }
}

-> 결과

-> 위 코드에서 Data data = new Data()에 final을 붙였다. 이러면 data가 final의 대상이 될 것이다. 그런데 data에 들어오는 값은 new 키워드를 통해 반환되는 Data인스턴스의 참조값이다. data는 이 Data인스턴스의 참조값이 저장되고 이 값이 변할 수 없게 되는 것이다. Data의 인스턴스 참조값이 x001이라면 data는 x001값으로 고정되는 것이다.

-> 따라서 data에 최초 한 번 참조값을 저장했으면 이후에 new 키워드로 새로운 인스턴스의 참조값을 저장할 수 없다.

-> 참조값이 고정이기 때문에 x001 위치의 인스턴스에 대해서만 다룰 수 있게 된 것이다.

-> data를 통해 value의 값은 언제든지 수정할 수 있다. 

 

 

 

 

★ 참고

김영한의 실전 자바 - 기본편

'Java' 카테고리의 다른 글

다형성1  (0) 2024.02.09
상속  (0) 2024.02.08
자바 메모리 구조 & static  (0) 2024.02.06
접근 제어자  (0) 2024.02.05
생성자  (0) 2024.02.01