220116 - static 변수와 메소드

Suntory·2022년 1월 16일
0

TIL

목록 보기
10/57

자바의 정석 6장 - 객체 지향 프로그래밍을 읽다가 static 변수와 메소드에 대한 가이드가 있어서 정리 차 글을 남겨봅니다.

미션을 하며 클래스를 만들 때마다 언제 private/public/protected(아직 써본 적이 없음..)을 써야할 지, static은 어떤 경우에 써야할 지에 대해서 불분명하다는 걸 느낍니다. IDE에서 빨간 줄을 그어 주면 그때서야 불 끄기 식으로 수정했던 기억이 있어서 이번 계기로 static이나 접근 제어자에 대한 지식을 정리해나가보려 합니다.

멤버 변수의 두 종류, 클래스 변수와 인스턴스 변수

클래스에 정의한 속성을 흔히 멤버 변수 또는 필드라고 합니다. 이 멤버변수들은 두 가지 종류로 나뉘는데, 바로 클래스 변수와 인스턴스 변수입니다.

클래스 변수

클래스는 같은 개념을 공유하는 객체들을 정의하기 위해 만든 틀이라고 생각합니다. 같은 개념을 공유하는 객체들끼리는 공통점이 존재할 확률이 높습니다. 그 클래스의 모든 인스턴스가 공통적으로 사용할 멤버 변수를 static을 붙여 모든 인스턴스에서 일정한 값을 유지하도록 만들어 줍니다. 이를 클래스 변수라고 합니다. 예를 들어 다음과 같은 Circle 클래스가 있을 수 있습니다.

class Circle {
	static double pi = 3.141592;
}

Circle 클래스의 인스턴스들은 각자의 넓이를 계산하기 위해 항상 일정한 값인 pi를 필요로 합니다. 이 경우, 클래스의 모든 인스턴스가 공통으로 사용하는 변수이기 때문에 static을 붙이면 관리가 쉬워질 것입니다. 만약 인스턴스마다 pi값을 초기화해서 사용하다보면, 어떤 사람은 3.14까지만 초기화하는 등 오차가 발생할 확률이 커질 것이라고 생각합니다. 또한, 임의로 클래스 변수를 재할당할 수 없도록 일반적으로 final 키워드를 같이 사용합니다.

인스턴스 변수

위와 같은 예로 Circle 클래스에 다음과 같은 인스턴스 변수를 추가합니다.

class Circle {
	static final double PI = 3.141592;
    	double radius;
    
    public Circle(double radius) {
    	this.radius = radius;
    }
}

클래스 변수인 pi와 달리, 원 인스턴들은 각자의 반지름을 가지게 됩니다. 그래서 인스턴스를 만들 때 생성자를 통해 각자의 반지름을 입력받고 초기화해서 사용하게 됩니다. 이처럼 static으로 선언된 값이 아니라 각 인스턴스마다 달라지는 값을 인스턴스 변수라고 합니다.

클래스 메서드와 인스턴스 메서드

멤버 변수와 마찬가지로 메서드도 static으로 선언할 것인지, 아닌지 정할 수 있습니다. 메서드 또한 만약 인스턴스와 관계없이 항상 클래스 전체에서 일정한 결과를 내놓는 메서드라면 static으로 선언하여 클래스 메서드로 만들 수 있습니다.

어떤 메서드를 정의하였는데 인스턴스를 필요로 하지 않는다면 static을 붙이는 것을 고려해보자. 메서드 호출 시간이 짧아지므로 성능이 향상될 수 있다.

반대로, 인스턴스 변수를 사용하는 경우에는 인스턴스마다 결괏값이 달라지므로 static으로 선언할 수 없습니다. static의 경우 클래스가 선언될 때 자동적으로 생성되는 속성인데, 인스턴스는 클래스 메서드가 호출되었을 때 존재하지 않을 수도 있기 때문입니다.

Static의 단점

그럼 Static 키워드는 무조건 좋은 걸까?라는 생각이 들었습니다. 간간히 static을 많이 사용하는 것은 좋지 않다라는 말을 많이 들었던 것 같아서 이번 기회에 조사를 해보았습니다. (참고자료 : Why are static variables considered evil)

1. 메모리 관리의 어려움

static 변수나 메서드는 클래스가 메모리에 올라갈 때 자동적으로 생성됩니다. 즉, 클래스 인스턴스가 만들어지지 않아도 계속 메모리를 차지한다. 심한 경우에는 그 변수나 메서드를 사용하지 않아도 Garbage collection도 일어나지 않고 메모리 상에서 존재합니다. 즉, 프로그래머가 메모리 관리를 할 수 있는 여지가 없이 static 변수는 프로그램과 lifecycle을 같이 합니다.

2. Thread-safe하지 않다.

멀티 쓰레드 환경에서 프로그램이 동작한다면 여러 쓰레드는 static 변수를 공유하게 됩니다. 이 때, static 변수를 편집하게 되면 동기화 문제가 발생할 수 있습니다. 이를 방지하기 위해 Synchronized 등의 처리를 하게 되면 결국 성능 이슈가 발생하게 됩니다.

3. 다형성을 확보할 수 없다.

단적으로 static 메소드는 오버라이드 할 수 없습니다. 메소드를 오버라이드하는 경우에 런타임에서 구현체를 찾아 다형성을 확보할 수 있지만, static 키워드가 붙은 변수는 컴파일 타임에 결정된 것을 사용하여 애초에 탐색의 대상이 아니기 때문입니다.

4. 리팩토링과 디버깅, 테스트에 불리하다.

static을 많이 사용하여 작성한 코드에서 무언가를 수정해야할 경우, 수정하는 데 드는 비용이 매우 많이 듭니다. 또한, 다양한 객체에서 사용되는 static 변수의 경우 디버깅하기 매우 어렵다는 단점이 있습니다.

싱글톤(Singleton) 패턴

그렇다면 static의 단점을 극복하면서 유사한 기능을 제공하는 것도 있을까요? 싱글톤 패턴을 사용하면 어느정도 단점을 커버하면서 클래스의 일정한 특성을 관리하는 것이 가능하다고 합니다.

싱글톤이란, 어떤 클래스의 인스턴스를 단 한개만 갖도록 하는 기법을 말합니다. 일반적으 클래스를 사용하기 위해서는 클래스의 생성자를 통해 새로운 객체를 만들어, 클래스에 접근합니다. 하지만 싱글톤에서는 생성자를 private 제어자를 통해 외부에서 접근할 수 없도록 합니다. 그럼 싱글톤 클래스는 어떻게 이용할까요? 싱글톤을 이용한 간단한 예시 코드를 작성했습니다.

class Student {

    private static Student instance;

    private Student() {
    }

    public static Student getInstance() {
        if (instance == null) {
            instance = new Student();
        }
        return instance;
    }
}

Student 클래스는 외부로부터 객체를 생성할 수 없고, 내부 객체에 접근도 할 수 없습니다. 오직 Static 메서드인 getInstance를 호출함으로써 클래스 내부에 가지고 있는 유일한 인스턴스를 return받아 사용이 가능합니다. 그럼 Student 객체끼리 공유해야 할 전역성은 유지되면서 static 키워드를 사용하지 않아도 되는 장점이 있습니다.

static 키워드의 단점이었던 오버라이드 불가능, 메모리 관리의 어려움 등을 극복 가능한 것이 싱글턴 패턴의 장점입니다. 물론 싱글턴을 제대로 구현하는 것이 어렵다는 문제가 있습니다. (동기화 관리 등..) 나중에 스프링에서 사용하는 싱글턴을 공부할 때 다시 한번 자세히 조사해봐야 겠습니다.

profile
천천히, 하지만 꾸준히 그리고 열심히

0개의 댓글