본문 바로가기

Studying/JAVA

[JAVA]_final

1. fianl

 

final 키워드는 이름 그대로 끝이라는 뜻.

변수에 final 키워드가 붙으면 더는 값을 변경할 수 없음.

 

 

■ final 변수와 상수1

 

▶ fianl_지역변수

package final1;

public class FinalLocalMain {
    public static void main(String[] args) {

        // final 지역 변수1
        final int data1;
        data1 = 10; // 최초 한 번만 할당 가능
        //data1 = 20; // compile error

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

    }

    static void method(final int parameter) {
        //parameter = 20;   // compile error
    }
}

[FinalLocalMain]

 

- final을 지역 변수에 설정할 경우 최초 한 번만 할당 가능. 이후 변수의 값을 변경하려면 컴파일 오류 발생.

- final을 지역 변수 선언 시 바로 초기화한 경우 이미 값이 할당되었으므로 값을 할당할 수 없음.

- 매개변수에 final이 붙으면 메서드 내부에서 매개변수의 값을 변경할 수 없음. 따라서 메서드 호출 시점에 사용된 값이 끝까지 사용됨.


▶ fianl_필드(멤버변수)

package final1;

public class ConstructInit {

    final int value;

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

public class FieldInit {

    static final int CONST_VALUE = 10;
    final int value = 10;

    /*public FieldInit(int value) {
        this.value = value;
    }*/ // compile error
}
package final1;

public class FinalFieldMain {
    public static void main(String[] args) {

        // final field - 생성자 초기화
        /*
        각 인스턴스마다 final 필드에 다른 값 할당 가능.
        물론 final 사용했기 때문에 생성 이후에 이 값을 변경하는 것은 불가능
         */
        System.out.println("생성자 초기화");
        ConstructInit constructInit1 = new ConstructInit(10);
        ConstructInit constructInit2 = new ConstructInit(20);
        System.out.println(constructInit1.value);
        System.out.println(constructInit2.value);

        // fianl field - 필드 초기화
        /*
        여기서 FieldInit 인스턴스의 모든 value 값은 10.
        생성자 초기화와 다르게 필드 초기화는 필드의 코드에 해당 값을 미리 저장함.
        모든 인스턴스가 같은 값 사용하기 때문에 결과적으로 메모리 낭비함.
        (이럴 때 사용하면 좋은 것이 바로 static영역.)
         */
        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을 필드에 사용할 경우 해당 필드는 생성자 통해 한 번만 초기화 가능.

 

[FieldInit]

 

- final 필드를 필드에서 초기화하면 이미 값이 설정되었기 때문에 생성자를 통해서도 초기화 불가능.

- 위 코드에서 보는 것처럼 static 변수에도 final 선언 가능. (상수 선언 시 대문자로 작성. 뒤에서 설명.)

 

[FinalFieldMain]

 

- ConstructInit과 같이 생성자 사용해서 final 필드 초기화 하는 경우, 각 인스턴스마다 final 필드에 다른 값 할당 가능.

  (물론 final 사용했기 때문에 생성 이후에 이 값을 변경하는 것은 불가능.)

- FieldInit 인스턴스의 모든 value 값은 10이 됨.

  그 이유는 생성자 초기화와 다르게 필드 초기화는 필드의 코드에 해당 값이 미리 정해졌기 때문.

- 모든 인스턴스 값을 사용하기 때문에 결과적으로 메모리 낭비하게 됨. 이럴 때 사용하면 좋은 것이 바로 static 영역.

 

○ static final

- final 키워드 사용해서 초기화 값이 변하지 않음.

- static 영역은 단 하나만 존재하는 영역으로, 메모리 비효율 문제를 해결 가능.

 

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


■ final 변수와 상수2

 

▶ 상수(Constant)

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

- 자바에서는 보통 단 하나만 존재하는 변하지 않는 고정된 값을 상수라고 함.

 

● 특징

- static fianl 키워드 사용.

- 대문자 사용하고 구분은 _(언더스코어)로 함_관례 : 일반적인 변수와 상수 구분하기 위함.

- 필드를 직접 접근해서 사용.

     - 상수는 기능이 아니라 고정된 값 자체를 사용하는 것이 목적.

     - 상수는 값을 변경 불가능. 따라서 필드에 직접 접근해도 데이터가 변하는 문제가 발생하지 않음.

 

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

[Constant]

 

- 애플리케이션에는 다양한 상수 존재 : 수학, 시간 등 실생활에서 사용하는 상수부터 애플리케이션의 다양한 설정 위한 상수

- 보통 이런 상수들은 애플리케이션 전반에서 사용되기 때문에 public 자주 사용. (특정 위치에서 사용하면 접근제어자 사용.)

- 상수는 중앙에서 값을 하나로 관리할 수 있는 장점이 있음.

- 상수는 런타임에 변경 불가능. 상수 변경하려면 프로그램 종료 후 코드 변경 한 뒤 프로그램 다시 실행.

 

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

프로그램 최대 참여자 수 : 1000
참여자 수 : 999
게임에 참여합니다.
참여자 수 : 1000
게임에 참여합니다.
참여자 수 : 1001
대기자로 등록합니다.

[ConstantMain1]_상수없음

 

이 코드에서의 문제점

- 만약 프로그램 최대 참여자 수 변경하면 두 군데를 변경해야 함.

- 코드만 봤을 때 1000이라는 숫자의 명확함을 한 번에 이해하기 어려움.

 

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

프로그램 최대 참여자 수 : 1000
참여자 수 : 999
게임에 참여합니다.
참여자 수 : 1000
게임에 참여합니다.
참여자 수 : 1001
대기자로 등록합니다.

[ConstantMain2]_상수있음

 

- Constant.MAX_USERS 상수 사용하여, 프로그램 최대 참여자 수를 변경해야 하면 Constant.MAX_USERS의 상수 값만 변경하면 됨.

- 1000이라는 숫자보다 MAX_USERS라는 변수명 코드 보면 이해하는데 더 쉬워짐.


▶ final 변수와 참조

 

- final은 변수의 값을 변경하지 못하게 막음. 여기에서 변수의 값은 무엇일까?

- 변수는 크게 기본형 변수와 참조형 변수가 있음.

     - 기본형 변수는 10, 20 같은 값을 보관하고, 값을 변경할 수 없음.

     - 참조형 변수는 객체의 참조값을 보관하고, 참조값을 변경할 수 없음.

 

package final1;

public class Data {
    public int value;
}
package final1;

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]

 

- int value는 final이 아니므로 변경할 수 있는 변수.

 

[FinalRefMain]

 

- 참조형 변수 data에 final이 붙음. 변수 선언 시점에 참조값을 할당했으므로 더는 참조값 변경 불가능.

- 하지만 참조 대상의 객체 값은 변경 불가능.

     - 참조형 변수 data에 final이 붙었는데 이 경우 참조형 변수에 들어있는 참조값을 다른 값으로 변경하지 못함.

     - 쉽게 말하면 다른 객체를 이제는 참조할 수 없음.

     - 이 말은 참조형 변수에 들어있는 참조값만 변경하지 못한다는 것이고, 변수 이외에 다른 곳에 영향을 주는 것은 아님.

- Data.value는 final이 아니므로 값은 변경할 수 있음.

 

 

정리하면 참조형 변수에 final이 붙으면 참조 대상 자체를 다른 대상으로 변경하지 못할 뿐, 참조하는 대상의 값은 변경 가능.


▶ 정리

 

- final은 매우 유용한 제약임.

- 만약 특정 변수의 값을 할당한 이후에 변경하지 않아야 한다면 final을 사용해야 함.

- 예제를 통해 살펴보기.

     - 고객의 id 변경하면 큰 문제가 발생한다고 했을 때 fianl로 선언하고 생성자로 값을 할당하기.

 

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;   // final로 지정했기 때문에 변경 불가 compile error
        this.name = name;
    }

    public void print() {
        System.out.println("id : " + id + ", name : " + name);
    }
}
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();
    }
}
실행결과

id : myId, name : kim
id : myId, name : seo

[Member]

 

- changeData() 메서드에서 final인 id 값 변경을 시도하면 컴파일 오류가 발생.

'Studying > JAVA' 카테고리의 다른 글