들어가며.
이전에 진행했던 프로젝트를 보면서 static 과 final 을 전혀 사용하지 않거나 의미 없이 사용한 부분이 너무 많다는 것을 알았습니다. 단순히 의존성 주입을 할 때만, 정적 팩토리 메서드를 작성할 때 등 의미를 모른체 사용해왔는데요.
이번 프리코스 피드백을 보면서 이 둘에 대한 사용처와 의미가 중요하다는 생각이 들었습니다. 그래서 2주차 과제에서는 적극적으로 적용해보고자 static 과 final 의 의미와 관계에 대해 작성해보겠습니다.
Static.
Static 은 '정적인', '고정된' 이라는 뜻을 가지고 있습니다. Static 키워드를 사용하여 정적 변수와 정적인 메소드를 만들어 낼 수 있죠. Static 을 사용한 변수와 메서드들은 Heap 영역이 아닌 Static 영역에 할당됩니다.
그렇기 때문에 Static 영역에 할당된 메모리는 모든 객체가 공유하여 어디서든지 참조할 수 있어 메모리 사용을 최적화하고 접근성을 높일 수 있다는 장점이 있습니다.
하지만 그에 따른 단점이 있는데 저희가 static 사용을 지양해야하는 이유는 다음과 같습니다.
- 메모리 문제
- static 은 프로그램 실행 시점에 할당을 하며 프로그램 종료 시점까지 메모리에서 해제되지 않는다.
- 동시성 이슈 문제
- static 은 전역에서 접근이 가능하므로 별도의 동기화 전략을 수립해야 한다.
- 런타임 다형성 불가
- static 으로만 이루어진 메소드를 사용하는 객체의 경우, 해당 객체를 메모리로 할당하여 사용하는 것이 아니고 객체, 메소드로 바로 접근하여 호출한다.
- 객체의 상태를 이용할 수 없다.
- 정적 메소드를 사용하기 위해서는 필요로 하는 인자를 모두 외부에서 주입해야 한다. 그 이유는 정적 메소드 안에서는 클래스의 인스턴스 필드를 사용할 수 없기 때문이다.
why? static 은 프로그램 시작 시점에 메모리에 올라가는데, 정적 메소드 안에 객체의 인스턴스 필드가 초기화되지 않았다면 문제가 생길 수 있다. 그래서 정적 메소드 안에는 정적 변수만 사용할 수 있다. - 일반 메소드라면 객체 내에 있는 상태를 통해 해당 메소드를 구현해 줄 수 있으므로 변화하는 상태에 따라 다채로운 기능 구현이 가능하지만, 결국 객체 내의 정적 메소드가 많아지면, 외부 값에 의존하는 수동적인 객체가 되어 버린다.
- 정적 메소드를 사용하기 위해서는 필요로 하는 인자를 모두 외부에서 주입해야 한다. 그 이유는 정적 메소드 안에서는 클래스의 인스턴스 필드를 사용할 수 없기 때문이다.
- 테스트하기 어려움
- 정적 필드는 전역으로 관리되기 때문에 프로그램 전체에서 이 필드에 접근하고 수정할 수 있게 되고 문제 발생 시 원인을 찾는데 어려움이 발생한다.
위 단점만 보면 static 은 절대 쓰면 안될 것처럼 보이지만 사용하기 좋은 상황이 있습니다.
예시
유틸리티 클래스 정의
유틸리티 클래스는 인스턴스 메소드와 인스턴스 변수를 제공하지 않고, 오직 데이터 처리를 위한 정적 메소드만 존재하는 클래스를 말합니다. 객체의 상태를 이용할 생각이 없고 오직 외부에서 받아온 값만을 사용하기에 여러 객체들의 필요에 의해 데이터를 처리하는 공통 로직이 필요할 때는 static 을 사용하여 설계합니다.
final.
final 키워드는 변수, 메소드, 클래스에 대한 변경을 금지합니다. 이를 불변성이라 하는데 이는 프로그램의 실행 도중 예상치 못한 값의 변경을 방지하고, 코드의 안정성을 높이는 데 기여합니다. 변경을 방지하는 코드이기 때문에 초기화하는 과정이 무조건 필요하며, 각 변수, 메소드, 클래스에 final 을 붙여서 어떠한 기능을 하는지 알아보겠습니다.
변수에서의 final
- 지역 변수
- final 로 선언된 지역 변수는 한 번만 값을 할당할 수 있다.
- 멤버 변수
- 객체의 멤버 변수에 final 을 사용하면 객체가 생성될 때 멤버 변수를 초기화해야 한다.
주의할 점은 final 변수는 재할당을 막아줄 수 있지만 객체의 내부 상태 변화는 막을 수 없다는 것입니다. 예를 들어, final 로 선언된 List 객체의 경우 다른 주소로의 재할당이 불가능하지만 내부 요소의 추가와 제거하는 것은 가능하다는 점입니다.
이를 해결하기 위해선 UnmodifiableList 라는 클래스를 사용하면 되는데 이 또한 완벽한 불변을 제공하는 것이 아니기 때문에 링크를 확인하고 해결하는 과정을 가지면 좋을 것 같습니다.
메서드에서의 final 사용
- final 로 선언된 메서드는 하위 클래스에서 Override 될 수 없다. 이를 통해, 상위 클래스의 기본 동작을 보존하고 변경되지 않게 한다.
클래스에서의 final 사용
- final 로 선언된 클래스는 다른 클래스가 상속할 수 없다. 즉, final 클래스는 확장될 수 없으며 항상 기본 형태를 유지한다. 이는 보안과 불변성을 유지하는 데 유용하다.
위 내용을 보면 final 클래스는 아래와 같은 장점을 가진다는 것을 알 수 있습니다.
- 컴파일 단계에서 잡아주기 때문에 안정성을 제공받는다.
- final 키워드는 코드를 읽는 사람에게 해당 요소가 변경되지 않을 것임을 명확하게 전달한다.
- 멀티스레드 환경에서 final 멤버 변수는 스레드 간에 안전하게 공유될 수 있다.
- final 변수는 읽기 전용이 되므로, 예상치 못한 변경으로부터 안전하다.
static 과 final 키워드 사용하기.
위에 작성한 static 은 지양하지만 final 에 대해선 사용함으로써 많은 이점을 가져가는 것을 알 수 있습니다. 이러한 final 의 불변성을 통해 static 의 단점인 사이드 이펙트 즉, 하나의 인스턴스 변화가 모든 인스턴스에 영향을 미치는 문제를 방지해 줄 수 있습니다.
상수 정의
절대 변하지 않는 변수를 상수라고 하는데, static final 로 선언함으로써 변하지 않는 값과 메모리에 프로그램 시작 시 한 번만 올려놓음으로써 자주 사용하는 상황에서 메모리를 아낄 수 있게됩니다.
private static final int Pi = 3.14;
이와같이 static 과 final 키워드를 적절히 활용하면, 코드의 안정성과 재사용성을 높이는 데 큰 도움이 되며 실수로 인한 값 변경을 방지할 수 있습니다.
참고자료
https://f-lab.kr/insight/understanding-java-static-final
https://steady-coding.tistory.com/603
'시리즈 > 자바' 카테고리의 다른 글
[JAVA] Enum 열거 타입에 대해 알아보기 ② (0) | 2024.10.31 |
---|---|
[JAVA] Enum 열거 타입에 대해 알아보기 ① (0) | 2024.10.29 |
[TEST] 테스트를 위한 JUnit 5 와 AssertJ ② (0) | 2024.10.25 |
[TEST] 테스트를 위한 JUnit 5 와 AssertJ ① (1) | 2024.10.24 |
[JAVA] 몰라서 못 썼던 정규 표현식을 알아보자 ④ (1) | 2024.10.22 |