자바생
article thumbnail
Published 2022. 3. 5. 16:01
final 키워드 Java
728x90

이번에 새싹 스터디에서 코드 리뷰에 대한 내용 중 static final을 사용할 지, final을 사용할 지 구분이 되지 않음

그래서 이번 기회에 final을 제대로 공부해보고자 정리해봄

final 키워드는 무엇인가요?

  • 재할당 불가를 명시하는 키워드
  • final 키워드는 변수, 메서드, 파라미터, 클래스에서 사용하는 “non-access modifier(비접근 수정자)” 로서, 각각 사용하는 context마다 사용법이 다릅니다.

context마다 사용법이 다르다고 말씀하셨는데 어떻게 다른가요?

  • primitive 변수는 한 번 초기화되고 이후에 수정할 수 없음
  • reference 변수는 다시 재할당할 수 없지만, 내부 값은 수정할 수 있음
  • 메서드는 overriding을 prevent함
  • 클래스는 상속을 prevent함
  • 파라미터는 해당 메서드에서 변경할 수 없음
        //primitive
        final int S = 4;

//        S = 5; 불가

        //reference
        final int[] ARRAY = {1, 2, 3};

//        ARRAY = new int[4]; 불가

        System.out.println("ARRAY[0] = " + ARRAY[0]);
        ARRAY[0] = 5;
        System.out.println("ARRAY[0] = " + ARRAY[0]);

//ARRAY[0] = 1
//ARRAY[0] = 5

그렇다면 final 변수는 언제 사용하는게 효과적일까요?

  • primitive 관점
    • 일반적으로 non-final 변수는 다시 할당할 수 있지만 final 변수는 재할당 즉, 변경할 수 없다
      • 프로그램 실행 내내 일정하게 유지하고자 하는 값에만 final 사용
  • reference 관점
    • reference final variable은 재할당할 수 없으나 해당 참조 변수가 가리키는 객체의 내부 상태는 변경될 수 있음
    • 내부 상태 변경은 재할당이 아님
    • 이러한 특징을 non-transitivity(비전이성, 비유동성).
  • final class
    • final class는 상속을 하지 못함
      • 예로 모든 Wrapper class는 final class임. 따라서 그것들을 상속하지 못함
    • String 클래스와 같이 불변 클래스를 만들기 위함.
      • final class가 아니면 불변 class를 만들 수 없음!

  • method
    • 오버라이딩을 할 수 없음
    • 따라서 상속을 해도 final 메서드는 오버라이딩 X
    • 부모의 메서드를 유지하고 싶어서 사용할 수 있음​

final을 사용함으로써 장점 중에 한가지만 말해주실래요?

  • 두 문자열을 concat하는 연산을 실행했을 때를 예로 들어봄
  • (String을 선언하는 방법은 리터럴과 객체가 있는데 여기선 리터럴로 차이를 보임)
    • String을 객체로 선언하면 불변이기 때문
  • 리터럴 변수일 때의 concat 연산과 final 이 붙은 변수일 때의 concat 연산에서 속도차이를 보임
  • 왜?
  • non-final 변수일 경우 컴파일러는 바이트코드를 생성하여 두 문자열을 연결
  • final 키워드로 인해 컴파일러가 문자열 연결 결과가 실제로 절대 변하지 않을 것이라는 걸 알음
  • 이 경우 concat 연산은 바이트코드로 변환하지 않고 바로 연산 실행
  • The string concatenation example demonstrates how the final keyword can help the compiler optimize the code statically.
💡 JIT 컴파일러와 연관이 있는걸까?? 왜 final 이 붙으면 바이트코드로 변환하지 않는 것이고, 붙지 않으면 변환되는 걸까?

 

public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();

        usingFinal();

        long end = System.currentTimeMillis();
        long timeFinal = end - start;

        long start2 = System.currentTimeMillis();
        usingNonFinal();
        long end2 = System.currentTimeMillis();
        long timeNonFinal = end2 - start2;

        long start3 = System.currentTimeMillis();
        usingNonFinal();
        long end3 = System.currentTimeMillis();
        long nonLiteral = end3 - start2;

        System.out.println("timeFinal = " + timeFinal);
        System.out.println("timeNonFinal = " + timeNonFinal);
        System.out.println("nonLiteral = " + nonLiteral);

    }
private static String usingFinal() {
        final String s1 = "a";
        final String s2 = "b";

        return s1 + s2;
    }

    private static String usingNonFinal() {
        String s1 = "a";
        String s2 = "b";

        return s1 + s2;
    }

    private static String usingNonLiteral() {
        String s1 = new String("a");
        String s2 = new String("b");

        return s1 + s2;
    }

/*
timeFinal = 0
timeNonFinal = 14
timeNonFinal22 = 14
*/

Effectively final 이 뭔가요?

  • effectively final은 final이 명시적으로 선언되진 않았지만
  • 초기화 후에 값이 변하지 않으면 사실상 final로 취급한다는 것
  • Java8부터 해당 용어가 생김

Effectively final이 왜 필요하나요?

  • final로 선언되지 않아도 람다들이 명시적으로 지역 변수를 사용하도록 하는 것
  • 하지만 컴파일러는 final이 아니기 때문에 정적 코드 최적화를 진행하지 않음(아마 앞에서 concat 얘기 비슷한 것일듯)
    • However, the Java compiler won't perform static code optimization for effectively final variables the way it does for final variables.

메서드에 final을 사용할 수 있다고 하셨는데 생성자에도 final을 사용할 수 있을까요? 그렇게 생각한 이유도 같이 말씀해주세요.

  • 생성자는 특별한 메서드임
  • 생성자는 상속되지않음
  • 따라서 오버라이딩이나 hiding이 안됨
  • 오버라이딩 안되기 때문에 수정될 가능성도 없음
  • 수정 가능성이 없기 때문에 수정을 제한하는 의미가 없음
  • 생성자는 본질적으로 수정할 수 없기 때문에 final 키워드가 필요없음
  • final을 쓰면 컴파일 에러 발생!

생성자에 static을 사용할 수 있을까요? 그렇게 생각한 이유도 같이 말씀해주세요.

  • static은 클래스의 객체가 아닌 클래스에 속하게 됨
  • 클래스는 정적임
  • 생성자는 클래스의 객체가 생성될 때 호출되는데, 애초에 성립이 안됨
  • 또한 정적 생성자를 선언하면 하위 클래스에선 생성자에 접근하거나 호출할 수 없음
  • 왜냐하면 static이 클래스 내에서 허용되지만 하위 클래스에는 허용되지 않음
    • static은 상속되지 않는다

final variable을 초기화하는 방법이 어떤 게 있을까요?

  • 초기화 블럭을 사용함
    • final은 normal block, static final은 static block
  • 생성자를 이용하여 초기화
  • 선언할 때 바로 할당
  • 중요한 점은 생성자나 블럭에서 할당하지 않으면 컴파일 오류 발생!
    private static final String A;
    private final String B;

    static{
        A = "a";
//        B = "b"; non-static variable B cannot be referenced from a static context
    }

    {
//        A = "a"; cannot assign a value to final variable A
        B = "b";
    }

static final과 non-static final의 차이를 말해주세요

  • final variable은 일단 수정 불가능함
  • static과 non-static의 차이는 클래스에 속하거나, 각 인스턴스(객체)마다 속해있음
  • static final
    • 클래스에 존재하면서 불변
  • non-static final
    • 클래스의 인스턴스 즉, 객체마다 존재하며 불변
💡 JVM에서의 final과 non-final 처리 방법이 다르다고 함. 나중에 JVM과 연관지어 설명한다면 더 좋을 듯?

 

정리

그러면 static final이 상수냐? final이 상수냐??

final의 정의를 잘 생각해보자

final은 “재할당 불가”를 명시하는 키워드

final

  • 재할당 불가 O
  • 객체 간에는 서로 다른 값을 가짐
  • 객체 내에서 재할당할 수 없는 변수이지만, 객체끼리 비교하면 값이 다를 수 있다.

static final

  • 재할당 불가 O
  • 한 클래스에서 불변함 즉, 모든 인스턴스에서 같은 값을 가짐

reference가 final인 경우, 클래스가 immutable하다면 상수, 그렇지 않다면 보장할 수 없음

  • immutable하지 않은데 static final을 붙일 이유가 없음
    • static final을 붙여도 내부 attribute 값을 변경할 수 있기 때문

애초에 상수라는 말이 헷갈린다,, 그냥 컴파일 타임 상수도 있고 런타임 상수도 있기 때문에

읽기 전용 변수라고 말하는게 덜 헷갈리는 것 같기도?

REFERENCES

final keywork in geeksforgeeks

final과 JIT 컴파일러의 관계 → final을 사용하면 같은 코드를 실행할 때 더 빠르다

728x90

'Java' 카테고리의 다른 글

Primitive Type, Reference Type, Literal  (0) 2022.05.03
Java volatile keyword  (0) 2022.04.18
스트림  (0) 2022.01.04
==와 equals의 차이  (0) 2021.05.25
String Operator '+' 의 작동 원리(2022.03.24 수정) Java 9  (0) 2021.05.25
profile

자바생

@자바생

틀린 부분이 있다면 댓글 부탁드립니다~😀

검색 태그