우테코 프리코스를 하면서 메소드 파라미터에 final 키워드를 붙이는 형식을 처음 접하게 되었다! 확인하고 까먹고 다시 찾을 게 뻔하기 때문에 기록한다.
목차
1. final 키워드란?
2. final 키워드가 항상 재할당이 불가할까?
3. 메소드 파라미터에 있는 final
java에서 final 키워드는 불변하는 상수 값이나 객체의 재할당을 막기 위해 사용한다.
public class Example {
final int num=0;
public void changeNum(){
num=1;
}
}
이와 같은 코드은 컴파일러가 에러를 발생시킨다.
정답은 아니다.
java의 reflection API를 사용하면 재할당할 수 있다.
final 키워드는 1번에서 봤던 코드와 같이 컴파일러 수준에서 에러를 발생시킨다.
하지만 Reflection은 컴파일 이후에 클래스로더가 메모리에 클래스 파일을 읽어들인 정보를 조작하는 것이다.
Spring 프레임 워크를 사용해본 사람은 이해하기에 더욱 수월할 것인데, 이때 Bean을 할당하는 DI가 Reflection을 사용한다. 정적인 언어인 JAVA의 큰 단점을 상쇄시킬 수 있는 강력한 기술이다.
백기선님 유튜브 리플렉션 강의 내용
리플렉션에 대해 짧게 설명된 강의인데 참고하기에 좋다.
리플렉션에 대해 깊게 공부하기에는 날을 잡고 해야될 것 같아, 간단하게 final 키워드를 리플렉션을 통해 변경해보겠다.
import org.junit.Assert;
import org.junit.Test;
import java.lang.reflect.Field;
public class ChangeFinal {
@Test
public void 기분_변경_테스트() throws IllegalAccessException, NoSuchFieldException {
Today today=new Today("Good");
Class<?> todayClass = Today.class;
Field field=todayClass.getDeclaredField("feeling");
field.setAccessible(true);
field.set(today, "Bad");
Assert.assertEquals("Bad", today.feeling);
}
static class Today{
final String feeling;
Today(String feeling){
this.feeling = feeling;
}
}
}
현재 Today 클래스의 feeling 변수가 컴파일 시점에 초기화되지 않았다. 생성자를 통해 런타임에 초기화되기 때문에 컴파일러에서 에러를 발생시키지 않는다. 결과적으로 런타임에 feeling 변수를 Good으로 설정하고 이후 Bad로 변경할 수 있다.
public class ChangeFinal {
@Test
public void 기분_변경_테스트() throws IllegalAccessException, NoSuchFieldException {
Today today=new Today();
Class<?> todayClass = Today.class;
Field field=todayClass.getDeclaredField("feeling");
field.setAccessible(true);
field.set(today, "Bad");
Assert.assertEquals("Bad", today.feeling);
}
static class Today{
final String feeling="Good";
Today(){
}
}
}
위의 코드는
이런 결과가 도출된다.
feeling 변수가 컴파일 시점에 초기화 되기 때문이다.
결론적으로, final 키워드가 붙었다고 해서 무조건 불변인 것은 아니다. 바꾸고 싶음 바꿀 수 있음! 하지만 final 키워드가 붙은 변수가 컴파일 시점에 초기화 된다면 변경할 수 없다.
포스팅을 쓰게 된 결정적인 계기인 메소드 파라미터의 final
public void changeParameter(final int a){
a=2;
}
파라미터에 final을 붙이는 건 매우 생소하다. 이 경우 컴파일러에서 a=2;를 수행하지 못하도록 한다.
메소드 수준에서 들어오는 파라미터에 대해 재초기화 하지 못하도록 하는 것이다.
이걸 왜 쓸까? ...
사실 막 엄청 중요한 기능은 아니라고 생각한다. 그럼에도 사용하는 이유는 코드를 읽는 사람이 더욱 이해하기가 쉬워진다는 것이다. final 키워드를 파라미터에 붙여줌으로써, "해당 코드에서 파라미터는 재초기화가 불가능하다." 라는 정보 값을 줄 수 있기 때문이다.
해당 스택오버플로우 질문에서도 엄청난 이점이 있는 건 아니고 그냥 보기에 좋다는 의견이 대다수다.
만약에 문자열에 대한 validation을 수행하는데, 해당 메소드에서 실수로 문자열을 수정한다면 이는 큰 문제가 발생할 것이다. 이때 파라미터를 애초에 final 키워드로 정의함으로써 이러한 문제를 발생시키지 않을 수 있다. (컴파일 타임에 확인 가능해서 문제 발생을 막을 수 있음)
public void validation(final String str){
str="new String";
}
-> 컴파일 타임에 에러 확인 가능
사용자 정의 클래스 같은 경우에는 해당이 안 된다.
예를 들어,
public class FS {
public void start(){
InnerClass innerClass=new InnerClass();
System.out.println(innerClass.x);
updateInnerClass(innerClass);
}
private void updateInnerClass(final InnerClass innerClass){
innerClass.x=3;
System.out.println(innerClass.x);
}
static class InnerClass{
int x=0;
}
}
이런 경우에는 final 키워드인 innerClass 주소값은 그대로고 주소값에 해당하는 인스턴스의 멤버 변수가 변한 것이기 때문이다.
결론적으로는 final 키워드를 통해서 더욱 명확하고 실수 없이 코딩할 수 있다는 것이 큰 장점이다. 또한 Reflection을 통하여 런타임에 할당된 final 키워드 변수는 불변하지 않다는 것을 알게 되었다.
<참고 포스팅>
https://unluckyjung.github.io/java/2022/02/04/java-reflection-change-final-value/
chat-gpt 다수 참고