static 은 언제 사용해야 할까?

@VERO
Created Date · 2024년 02월 01일 04:02
Read Time · 11 min read
Last Update · 2024년 02월 01일 05:02

static modifier 란?

내용은 오라클 Java Tutorials에서 참고했다.

모든 객체들에서 공통으로 적용되는 변수를 선언하고 싶을 때, static 을 사용하면 된다.
선언에 static modifier 가 있는 필드를 static field 또는 클래스 변수라고 한다. 해당 변수는 어떤 객체가 아니라 클래스와 연관된다.
클래스의 모든 인스턴스는 메모리의 고정된 한 위치에 있는 클래스 변수를 공유한다. 모든 객체는 클래스 변수의 값을 변경할 수 있지만, 클래스 변수는 클래스의 인스턴스를 생성하지 않고도 조작할 수 있다.

Java 는 static 변수 뿐만 아니라 static 메서드도 지원한다. 선언에 static modifier 가 있는 static 메서드는 클래스의 인스턴스를 생성할 필요 없이 클래스 이름으로 호출해야 한다. 물론 객체 참조로 static 메서드를 참조할 수도 있지만, 클래스 메서드라는 것을 명확히 알 수 없으므로 권장하지 않는다.

Constants

static modifier 는 final modifier 와 함께 상수를 정의하는 데에도 사용된다. final modifier 는 해당 필드의 값을 변경할 수 없음을 나타낸다.
static final 방식으로 정의된 상수는 재할당할 수 없으며, 프로그램에서 재할당을 시도하면 컴파일 타임 에러가 발생한다. 관례에 따라 상수 값의 이름은 대문자로 표기한다.

원시 타입이나 문자열이 상수로 정의되어 있고 컴파일 타임에 값을 알 수 있는 경우, 컴파일러는 코드의 모든 곳에서 상수 이름을 해당 값으로 바꾼다. 이를 컴파일 타임 상수라고 한다. 외부 세계의 상수 값이 변경되면 (PI 값이 3.14 에서 3.2가 된다거나) 이 상수를 사용하는 모든 클래스를 다시 컴파일 하여 현재 값을 가져와야 한다.

static 을 왜 지양해야 하는가?

변경 지점 추적의 어려움

변경 가능한 static 변수를 사용하게 되면 인스턴스를 생성하지 않고도 해당 변수를 조작할 수 있는 전역 상태가 된다. 그렇기 때문에 여러 곳에서 static 변수의 값 또는 상태를 조작할 수 있게 되는데, 이런 경우 어느 곳에서 변수를 조작했는지 추적하기 어렵다는 단점이 있다.

테스트의 어려움

일반적인 테스트 방식들은 테스트 대상 객체의 종속성을 외부에서 주입하는 것이다. 이를 통해 테스트 중인 객체를 격리시켜 테스트의 정확도를 높일 수 있다. 그러나 static 메서드나 변수는 외부에서 주입하기 어렵다. static 메서드는 클래스에 직접 속해 있어 이를 사용하는 객체에서 분리하기 어렵고, static 변수는 전역 상태를 가지기 때문에 테스트 환경에서 격리하기 어렵다.

또한 static 변수는 전역 상태를 유지하므로, 한 테스트 케이스에서의 변경이 다른 테스트 케이스에 영향을 미칠 수 있다. 이는 테스트의 독립성을 해칠 뿐만 아니라 예측 불가능한 결과를 초래할 수 있다.

테스트 전에 특정 상태로 초기화하기 어렵다는 문제도 있다. 이를 해결하기 위해서는 테스트 실행 전후에 static 변수의 상태를 적절히 설정하고 정리해야 한다.

객체지향 프로그래밍의 기본 원칙과 상충되는 구현 방식

캡슐화

캡슐화는 객체의 데이터와 데이터를 처리하는 메서드를 하나의 단위로 묶는 것을 말한다. 이를 통해 데이터의 직접적인 접근을 제한하고, 객체 내부의 구현을 숨기며, 외부 인터페이스만을 통해 상호작용하도록 한다.
static 변수나 메서드는 객체의 상태와 무관하게 클래스 레벨에서 정의된다. 이는 객체의 개별적인 상태를 나타내는 캡슐화의 원칙을 위반한다. static 변수는 모든 객체 인스턴스에 의해 공유되므로, 객체별로 독립적인 상태를 유지하기 어렵다.

상속

상속은 하나의 클래스가 다른 클래스의 속성과 메서드를 이어받을 수 있게 한다.
static 메서드나 변수는 상속되지만, 상속받은 클래스에서는 오버라이딩이 불가능하다. 즉, 자식 클래스에서 부모 클래스의 static 메서드나 변수의 동작을 변경하거나 확장하는 것이 어렵다. 상속을 통한 유연성과 확장성을 제한하는 것이다.

다형성

static 메서드는 클래스 레벨에 바인딩된다. 즉, 이 메서드들은 클래스에 속하며, 인스턴스에 속하지 않는다. 그런데 오버라이딩은 상속을 통해 부모 클래스의 인스턴스 메서드를 자식 메서드에서 재정의하는 것이다. static 메서드는 인스턴스가 아닌 클래스에 속하기 때문에 인스턴스의 다형성과 관련이 없다. 결론적으로 static 메서드는 오버라이딩 될 수 없다.
(자식 클래스에서 같은 이름과 시그니처를 가진 static 메서드를 선언하는 것은 부모 클래스의 메서드를 오버라이딩하는 것이 아니라 숨기는 것이다.)

다형성은 같은 인터페이스나 부모 클래스에 속하는 다양한 객체들이 동일한 메시지에 대해 각기 다른 방식으로 반응할 수 있도록 하는 원리이다. 인스턴스 메서드는 실행 시점에 객체의 실제 타입에 따라 다르게 동작할 수 있다. 그러나 static 메서드 호출은 컴파일 타임에 결정되며, 클래스 타입에 의해 결정된다. 따라서 부모 클래스 타입의 참조 변수가 자식 클래스의 인스턴스를 참조하더라도, static 메서드 호출은 항상 부모 클래스의 메서드를 호출한다.

이런 부분에서 static 메서드는 객체의 상태와 동작을 나타내는 객체지향 프로그래밍의 다형성 원칙과 상충된다고 할 수 있다.

멀티스레드 환경에서 발생할 수 있는 문제

static 변수는 모든 스레드에 의해 공유된다. 동시에 여러 스레드가 변수에 접근하면 동기화 문제가 발생할 수 있으며, 데이터 일관성과 안정성 문제가 발생할 수 있다.

어떤 경우에 static 을 사용해야 할까?

Stackoverflow에서도 많은 의견이 나오고 있다. 모든 상황에 맞는 방식이 아니므로, 각자의 상황에 맞춰 잘 판단해야 한다.

static 변수 / 메서드가 클래스 자체에 속할 때, 전역으로 관리해야 할 때

해당 변수나 메서드가 특정 인스턴스에 속하지 않고, 클래스 전체와 관련된 상태나 동작이 필요한 경우, static 변수나 메서드를 사용하는 것이 좋다.

Ex. 클래스 인스턴스의 개수를 추적할 때, 특정 클래스와 관련된 전역 설정을 관리할 때

static 변수가 상태를 갖지 않는 상수일 때

변경되지 않는 고정 값이나 애플리케이션에서 공통으로 사용되는 설정 값들을 정의할 때 static 변수를 사용한다.
이런 상수들은 인스턴스마다 다르게 유지될 필요가 없기 때문에 클래스 레벨에서 관리되는 것이 좋다.

Ex. PI 값, 설정 값

싱글톤 패턴을 구현해야 할 때

전역적으로 하나의 인스턴스만을 유지해야 할 때, 싱글톤 패턴을 사용할 수 있다. 인스턴스 자체를 static 변수로 유지하여 어디서나 동일한 인스턴스에 접근할 수 있도록 한다.

Ex. 구성 관리, 로깅, 데이터베이스 접속 관리 등