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