자바에서 final에 대한 이해

2021. 4. 7. 18:04 JAVA/Java

1. 개요

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를 참고해주세요. 

 

 

참고

 

 

출처 : advenoh.tistory.com/13