Java에서는 final 키워드를 사용하면 불변의 특징을 가지게 된다.
불변이란?
- 값을 수정하지 못하는 특징을 가진다.
- 그러기에 동일한 데이터를 제공할 수 있다.(thread-safe 특징을 가진다.)
즉 한 번 할당하면 더 이상 해당 값을 수정하지 못하게 된다.
final은 변수, 클래스, 메서드에 붙일 수 있다.
1. 파라미터에 붙이는 경우
public void fun(final int num){
num = 3;
}
num = 3을 하는 부분에서 컴파일 에러가 발생한다.
이미 파라미터로 넘어온 num에 final이 붙어서 더 이상 재할당이 불가능한 상태.
2. 변수에 final 붙이는 경우
class Car{
private String name;
private final int price;
public Car(String name, int price) {
this.name = name;
this.price = price;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(int price) {
this.price = price;
}
}
price에 final을 붙여서 불변 상수로 만든 다음에 set을 진행하면 컴파일 에러가 발생한다.(변경 불가)
또한 생성자에 final 붙은 필드가 반드시 들어있어야 한다. 해당 필드 없이 생성이 불가능해진다.
3. 클래스에 final 붙이는 경우
public final class Car {
private String name;
private int price;
}
Car 클래스에 final을 붙이게 되면 해당 클래스는 더 이상 상속이 불가능하게 된다.
Tico 클래스에 Car를 상속하려고 했더니 final이라 불가능하게 된다.
4. 메서드에 final 붙이는 경우
메서드에 final을 붙이게 되면 오버라이딩이 불가능해진다.
오버라이딩이란?
상속, 구현받은 메서드를 재정의하는 기능을 의미한다.
public class Car {
private String name;
private int price;
public final void Sound(){
System.out.println("빵빵");
}
}
부모의 메서드에 final 키워드를 붙이고 자식에서 오버라이드를 진행하면
빨간 줄과 함께 컴파일 에러가 발생한다. 즉 오버라이드 불가능.
여기서 짚고 넘어갈 개념이 fianl = 완전 불변은 아니다. 내부 값은 변경이 가능하다.
final List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
위 코드는 정상적인 코드이다. list 자체는 final을 붙여서 재할당이 불가능하지만, 내부 값은 변경이 가능해진다.
list = new ArrayList<>();
반면에 위 코드는 재할당을 시도했기에 컴파일 에러가 발생한다.
그러면 final을 붙이면 컴파일 과정에서는 어떻게 취급이 될까?
public class Car {
private String name;
private int price;
private int distance;
public Car(String name, int price, int distance) {
this.name = name;
this.price = price;
this.distance = distance;
}
}
이렇게 만들고 price에 final을 붙이면서 바이트 코드를 비교해 봤다.
왼쪽이 안 붙였을 때, 오른쪽이 final 붙였을 때인데 다른 점은 6번째 줄 final이 붙었다는 것 말고는 차이가 없었다.
객체를 생성해서 비교해도 바이트 코드는 똑같았다.
그래서 한 가지 실험을 진행해 보았다.
final을 붙인 int와 붙이지 않은 int를 1억 번 더하는 연산을 각각 진행하고 시간 측정을 했다.
long num = 1;
final long finalNum = 1;
long sum = 0;
Long startTime = System.nanoTime();
for(int i=0; i<100000000; i++){
sum += num;
}
Long endTime = System.nanoTime();
System.out.println("final 없을 때 걸린 시간 : " + String.valueOf(endTime - startTime));
sum = 0;
startTime = System.nanoTime();
for(int i=0; i<100000000; i++){
sum += finalNum;
}
endTime = System.nanoTime();
System.out.println("final 있을 때 걸린 시간 : " + String.valueOf(endTime - startTime));
결과는 어떻게 나올까?
대체 왜 이런 결과가 나오는 걸까??
찾아보니 final 키워드를 붙이면 컴파일 단계에서는 해당하는 값을 이미 알게 된다.
인라이닝 키워드가 사용되는데, 변수를 호출하는 대신 실제 값을 호출하는 것이다.
즉 final int num = 1을 선언하고 sum += num을 하면 sum += 1로 변환이 되는 것.
실제 값에 바로 접근하게 돼서 속도가 빨라지게 된다.
또 JVM에는 실행 엔진이 2개가 존재한다
- 인터프리터
- JIT 컴파일러
여기서 JIT 컴파일러는 자주 사용되는 코드의 경우 최적화를 통해 시간을 단축시키는데, 이러한 점이 이용됐을 수도 있다고 생각한다.
만약에 메서드(클래스)에 final을 붙였다면?
메서드가 오버라이딩이 안 될 것을 알고 있기에, 메서드 호출을 더 효율적으로 처리하게 된다. 클래스도 마찬가지로 상속이 불가능하기에 해당 클래스의 모든 메서드가 final로 취급이 가능해진다.
결과적으로 final 키워드를 잘 사용하면 속도 향상을 기대할 수 있을 것 같다.
'CS지식' 카테고리의 다른 글
Interceptor와 Filter는 뭐가 다를까? (0) | 2024.05.06 |
---|---|
스프링 IOC와 DI를 어떻게 설명해야 할까? (0) | 2024.05.05 |
HTTPS 개념을 잡고 가자..(feat. SSL/TLS을 곁들인) (0) | 2024.03.16 |
Ajax, axios, fetch 특징 (0) | 2023.05.11 |