1. 개요
final int MAX = 5;
위와 같이 final 키워드를 떠올릴 때면 그냥 상수로만 생각할 때가 종종 있습니다. final을 클래스, 메서드, 변수에 선언하면 조금씩 할 수 있는 부분들이 제안됩니다. 너무 당연한 내용이지만, 시간이 지니니까 기억에서 사라져버려서 이번에 다시 한번 상기하기 위해 정리를 해보았습니다.
자바에서 final 키워드는 여러 컨텍스트에서 단 한 번만 할당될 수 있는 entity를 정의할 때 사용됩니다. ( 위키피니아 )
final 키워드는 총 3가지에 적용할 수 있습니다. 각각에 대해서 세부적으로 알아보죠.
-
final 변수
- 원시 타입
- 객체 타입
- 클래스 필드
- 메서드 인자
- final 메서드
- final 클래스
2. Final 변수
2.1 원시 타입
로컬 원시 변수에 final로 선언하면 한번 초기화된 변수는 변경할 수 없는 상수값이 됩니다.
@Test
public void test_final_primitive_variables() {
final int x = 1;
x = 3; //한번 assign되면 변경할 수 없음.
}
2.2 객체 타입
객체 변수에 final로 선언하면 그 변수에 다른 참조 값을 지정할 수 없습니다. 원시 타입과 동일하게 한번 쓰여진 변수는 재변경 불가합니다. 단, 객체 자체가 immutable하다는 의미는 아닙니다. 객체의 속성은 변경 가능합니다.
@Test
public void test_final_reference_variables() {
final Pet pet = new Pet();
pet = new Pet(); 다른 객체로 변경할수 없음
pet.setWeight(3); //객체 필드는 변경할 수 있음
}
2.3 메서드 인자
메서드 인자에 final 키워드를 붙이면, 메서드 안에서 변수값을 변경할 수 없습니다.
public class Pet {
int weight;
public void setWeight(final int weight) {
weight = 1; //final 인자는 메서드안에서 변경할 수 없음
}
}
2.4 맴버 변수
클래스의 맴버 변수에 final로 선언하면 상수값이 되거나 write-once 필드로 한 번만 쓰이게 됩니다. final로 선언하면 초기화되는 시점은 생성자 메서드가 끝나기 전에 초기화가 됩니다. 하지만, static이냐 아니냐에 따라서도 초기화 시점이 달라집니다.
-
static final 맴버 변수 (static final int x = 1)
- 값과 함께 선언시
- 정적 초기화 블록에서 (static initialization block)
-
instance final 맴버 변수 (final int x = 1)
- 값과 함께 선언시
- 인스턴스 초기화 블록에서 (instance initialization block)
- 생성자 메서드에서
2.4.1 인스턴스 초기화 블록 vs 정적 초기화 블록
정적 초기화 블록과 인스턴스 초기화 블록의 차이점을 간단하게 알아봅니다.
인스턴스 초기화 블록 | 정적 초기화 블록 |
---|---|
객체 생성할때마다 블록이 실행됨 부모 생성자이후에 실행됨 생성자보다 먼저 실행됨 |
클래스 로드시 한번만 블록이 실행됨 |
@Test
public void initializeBlockTest() {
Cat.s_value = 5; static //초기화 블록 호출됨
System.out.println("s_value: " + Cat.s_value);
System.out.println("");
System.out.println("Cat 객체 생성1");
Cat cat1 = new Cat();
System.out.println("");
System.out.println("Cat 객체 생성2");
Cat cat2 = new Cat();
}
public class Pet {
public Pet() {
System.out.println("super construtor : Pet");
}
}
실행결과는 다음과 같습니다. 정적 초기화 블록은 클래스가 로딩되는 시점에 한 번만 호출되고 static 블록 안에서 static 맴버변수를 초기화 할 수 있습니다.
인스턴스 초기화 블록은 객체를 생성할 때마다 호출되고 자식 객체의 생성자가 호출되기 전에 그리고 부모 생성자 이후에실행됩니다.
public class Cat extends Pet {
final int i_value;
static int s_value;
{
System.out.println("instance initializer block");
i_value = 3;
System.out.println("i_value: " + i_value);
System.out.println("s_value: " + s_value);
}
static {
System.out.println("static initializer block");
// System.out.println("i_value: " + i_value); //static 블록에서 필드 접근 안됨
System.out.println("s_value: " + s_value);
}
public Cat() {
System.out.println("contructor: Cat");
}
}
내용이 길었네요. 다음은 메서드와 클래스에 final을 선언하면 어떤 차이가 있는지 알아봅시다.
3. Final 메서드
메서드를 final로 선언하면 상속받은 클래스에서 오버라이드가 불가능하게 됩니다. Dog 객체는 Pet의 makeSound() 메서드를 재정의할 수 없습니다. 언제 사용하면 좋을까요? 구현한 코드의 변경을 원하지 않을 때 사용합니다. side-effect가 있으면 안 되는 자바 코어 라이브러리에서 final로 선언된 부분을 많이 찾을 수 있습니다.
public class Pet {
public final void makeSound() {
System.out.println("ahaha");
}
}
public class Dog extends Pet {
//final로된 메서드는 override할수 없음
public void makeSound() {
}
}
4. Final 클래스
클래스에 final을 선언하면 상속 자체가 안됩니다. 그냥 클래스 그대로 사용해야 합니다. Util 형식의 클래스나 여러 상수 값을 모아둔 Constants 클래스을 final로 선언합니다.
public final class Pet {
}
Pet 클래스가 final 클래스로 선언되어 상속할 수 없음
public class Dog extends Pet {
}
4.1 상수 클래스
상수 값을 모아준 클래스는 굳이 상속해서 쓸 이유는 없겠죠?
public final class Constants {
public static final int SIZE = 10;
}
public class SubConstants extends Constants {
}
4.2 Util 형식의 클래스
JDK에서 String도 final 클래스로 선언되어 있습니다. 자바의 코어 라이브러리이기 때문에 side-effect가 있으면 안 되겠죠. 다른 개발자가 상속을 해서 새로운 SubString을 만들어 라이브러리로 다른 곳에서 사용하게 되면 유지보수, 정상 실행 보장이 어려워질 수 있습니다.
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
}
여기서에 작성된 코드는 github 를 참고해주세요.