2024. 2. 7. 18:45ㆍJava
◎ 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의 값은 언제든지 수정할 수 있다.
★ 참고