[JAVA 객체지향프로그래밍] static과 싱글톤 패턴

    반응형

     

    이 글은 패스트 캠퍼스 한번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 강의를 듣고 공부한 내용을 바탕으로 정리한 글입니다.


     

    1. static 변수

    static 변수는 여러 인스턴스가 공유할 수 있는 변수입니다. 서로 다른 여러개의 인스턴스들이 공통으로 사용하는 변수가 필요한 경우, static 변수를 사용할 수 있습니다.

    예를 들어, 학생마다 새로운 학번을 생성하거나 카드회사에서 카드를 새로 발급할 때마다 새로운 카드 번호를 부여해야할 때 static 변수를 사용해야겠습니다.

    • 인스턴스가 생성될 때 만들어지지 않고, 프로그램이 처음 메모리에 로딩될 때 메모리를 할당받습니다.
    • 클래스 변수, 정적 변수라고도 합니다. (cf. 인스턴스 변수)
    • 인스턴스 생성과 상관없이 사용 가능하므로 클래스 이름으로 직접 참조하여 사용합니다.

    2.static 변수 선언과 사용

    고유한 사번이 필요한 Employee 클래스를 만들어보겠습니다.

    Employee 클래스의 생성자에서 인스턴스가 생성될 때마다 static 변수인 serialNum을 증가시키도록 하여 employeeId에 복사해 초기화하도록 작성하였습니다.

    public class Employee {
    
        public static int serialNum = 1000;
        private int employeeId;
        private String employeeName;
        private String department;
    
        public Employee() {
            serialNum++;
            this.employeeId = serialNum;
        }
    
        public int getEmployeeId() {
            return employeeId;
        }
    
        public void setEmployeeId(int employeeId) {
            this.employeeId = employeeId;
        }
    
        public String getEmployeeName() {
            return employeeName;
        }
    
        public void setEmployeeName(String employeeName) {
            this.employeeName = employeeName;
        }
    
        public String getDepartment() {
            return department;
        }
    
        public void setDepartment(String department) {
            this.department = department;
        }
    
        @Override
        public String toString() {
            return "Employee{" +
                    "employeeId=" + employeeId +
                    ", employeeName='" + employeeName + '\'' +
                    ", department='" + department + '\'' +
                    '}';
        }
    }
    public class EmployeeTest {
        public static void main(String[] args) {
            Employee employeeObj1 = new Employee();
            employeeObj1.setEmployeeName("employeeObj1");
            System.out.println(employeeObj1.toString());
    
            Employee employeeObj2 = new Employee();
            employeeObj2.setEmployeeName("employeeTest2");
            System.out.println(employeeObj2.toString());
        }
    }

    employeeId가 중복되지 않고 증가하여 설정된 것을 확인할 수 있습니다.

    이때 serialNum에 직접 접근할 때에는 객체가 아닌 클래스를 참조해서 접근하도록 합니다. (Employee.serialNum)

    3. static 메서드

    위의 예시에서 serialNum은 중요한 변수이므로 public이 아닌 private으로 선언하겠습니다. 따라서 serialNum의 getter 메서드를 public으로 제공합니다. 이때 serialNum은 함부로 set하면 안되기 때문에 setter는 제공하지 않습니다.

    public class Employee {
    
        private static int serialNum = 1000;
    
        public static int getSerialNum() {
            return serialNum;
        }
    
        ...
    
    }

    이때, getSerialNum() 같이 static 변수를 반환하는 메서드를 static 메서드라고 합니다. (클래스 메서드, 정적 메서드라고도 부릅니다.) static 메서드는 다음 코드와 같이 클래스 이름으로 호출합니다.

    System.out.println(Employee.getSerialNum());

    위의 예시에서 serialNumpublic으로 선언하여 직접 접근하는 것보다(Employee.serialNum) 훨씬 제한적으로 정보를 공개합니다.

    static 메서드에서는 인스턴스 변수(일반 멤버 변수)를 사용할 수 없스니다. 바로 앞에서 본 것처럼 static 메서드는 인스턴스 생성과 무관하게 클래스 이름으로 호출될 수 있는데, 인스턴스 생성 전에 호출 될 수 있으므로 static 메서드 내부에서는 인스턴스 변수를 사용할 수 없습니다. 즉, 인스턴스를 생성하기도 전에 인스턴스 변수에 접근한 것이므로 할당되지 않은 메모리에 접근하는 것이므로 당연히 에러가 날 수밖에 없습니다. static 메서드 내에서 인스턴스 변수에 접근하면 아래와 같은 오류가 납니다.

    (일반 메서드에서 static 변수에 접근하는 것은 당연히 언제나 가능합니다. static 변수의 경우 프로그램이 로드될 때 메모리가 이미 잡혀있기 때문입니다.)

    (static 메서드 내의 지역 변수는 사용 가능합니다. 지역변수는 함수가 시작될 때 메모리가 잡히고 종료되면 바로 메모리가 해제되기 때문입니다.)

    4. 변수의 유효 범위(scope)와 메모리

    변수의 유효 범위(scope)와 생성/소멸(life cycle)은 변수 유형에 따라 다릅니다. 아래는 지역 변수, 멤버 변수, static 변수의 scope와 lifecycle을 나타낸 표입니다.

    변수 유형 선언 위치 사용 범위 메모리 생성과 소멸
    지역 변수
    (로컬 변수)
    함수 내부에 선언 함수 내부에서만 사용 스택 함수가 호출될 때 생성되고 함수가 끝나면 소멸됨
    멤버 변수
    (인스턴스 변수)
    클래스 멤버 변수로 선언 클래스 내부에서 사용하고private이 아니면 참조 변수로 다른 클래스에서 사용 가능 인스턴스가 생성될 때 생성되고, GC(Garbage Collector)가 메모리르 수거할 때 소멸됨
    vstatic 변수
    (클래스 변수)
    static 예약어를 사용하여클래스 내부에 선언 클래스 내부에서 사용하고private이 아니면 참조 변수로 다른 클래스에서 사용 가능 데이터 영역 프로그램이 처음 시작할 때 데이터 영역에 생성되고 프로그램이 끝나고 메모리를 해제할 때 소멸됨
    • static 변수는 프로그램이 메모리에 있는동안 계속 메모리를 차지하므로 너무 큰 메모리를 할당하는 것은 좋지 않습니다. 예를 들어 큰 배열을 static 변수로 선언하는 것은 좋지 않습니다.
    • 클래스 내부의 여러 메서드에서 사용하는 변수는 멤버 변수로 선언하는 것이 좋습니다. 즉, 객체의 속성으로 취급될 수 있는 것은 멤버 변수로 사용해야 합니다.
    • 멤버 변수가 너무 많으면 인스턴스 생성 시 쓸데없는 메모리가 할당됩니다. 그렇다고 멤버 변수를 너무 적게 선언하면 메서드에서 계속 매개변수로 넘겨주어야 하는 값이 많아지겠지요. 이또한 좋지 않습니다. 따라서 멤버 변수는 적당히 선언해야 합니다.

    이런 유의점은 다른 언어들도 마찬가지입니다. 상황에 적절하게 변수를 사용해야겠습니다!

    5. 싱글톤 패턴

    static 변수와 static 메서드에 대해 공부하였으니 이를 응용한 디자인 패턴인 싱글톤 패턴에 대해 공부해보겠습니다. 싱글톤 패턴이란, 프로그램에서 인스턴스가 단 한 개만 생성되어야 하는 경우 사용하는 디자인 패턴입니다.

    예를 들어, 한 회사 내에서 '회사'라는 객체는 유일하게 단 하나만 존재해야겠지요. 또, timezone에 결정되는 시간 인스턴스도 여러 개가 되서는 안됩니다.

    예시와 함께 싱글톤 패턴 구현에 대해 설명해보겠습니다. 싱글톤 패턴 구현 방법에도 여러가지가 있다고 하는데, 저는 객체를 미리 생성해두고 가져오는 방법으로 회사(Company) 객체를 구현해보겠습니다.

    public class Company {
    
        private static Company instance = new Company();
        private Company() {}
    
        public static Company getInstance() {
            if (instance == null)
                instance = new Company();
            return instance;
        }
    }
    1. 생성자는 외부에서 접근해서 새로운 객체를 만들지 못하도로고 private으로 선언합니다.
    2. 멤버 변수로 인스턴스를 생성합니다. 멤버 변수로 선언된 인스턴스도 private으로 선언하여 외부에서 직접 접근할 수 없도록 합니다.
    3. 생성된 인스턴스를 반환하는 public 메서드를 정의합니다.
      이때, getInstance() 메서드를 static 메서드로 정의하는 이유는 다른 클래스에서 인스턴스를 만들지 않고도 클래스명에 바로 참조해서 메서드를 사용할 수 있도록 하기 위해서 입니다.

    메인 함수를 실행시켜 확인해보겠습니다.

    public class CompanyTest {
        public static void main(String[] args) {
            Company company1 = Company.getInstance();
            Company company2 = Company.getInstance();
    
            System.out.println(company1);
            System.out.println(company2);
        }
    }

    메인 함수에서 Company 객체가 모두 같은 인스턴스인 것을 확인할 수 있습니다.

    이런 싱글톤 패턴을 쓰는 자바의 클래스가 있는데요, 바로java.utilCalendar 클래스입니다.

    import java.util.Calendar;
    
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();
    }

    Calendar 객체는 new 키워드로 객체를 만들 수 없고 getInstance() 메서드로 객체를 가져와야 합니다.

    반응형

    댓글