static (정적)

박영준·2023년 7월 3일
0

Java

목록 보기
101/111

1. 정의

  • static 클래스 변수 (참고: 변수, 상수, 리터럴 - 3. 종류)

  • "static" = "공통적인", "클래스의"

    • Static 변수 = 정적 필드
    • Static 메소드 = 정적 메소드
    • 이 둘을 합쳐 정적 멤버(클래스 멤버)라도 한다.
  • static 키워드를 붙이면, Java 가 메모리 할당을 딱 한번만 하도록 만듦

    • 메모리 사용에 이점
  • 정적 멤버는 객체(인스턴스)에 소속된 멤버가 아니라 클래스에 고정된 멤버
    → 따라서, 클래스 로더가 클래스를 로딩해서 메소드 메모리 영역에 적재할때 클래스별로 관리된다.
    → 따라서, 클래스의 로딩이 끝나는 즉시 바로 사용 가능

2. Static영역

Static 키워드를 통해 생성된 정적 멤버들은 Heap영역이 아닌 Static영역에 할당된다.
(new 연산을 통해 생성한 객체는 Heap영역에 생성된다.)

장점
Static 영역에 할당된 메모리는 모든 객체가 공유하여, 하나의 멤버를 어디서든지 참조할 수 있다.

단점
Garbage Collector의 관리 영역 밖에 존재하므로, Static영역에 있는 멤버들은 프로그램의 종료 시까지 메모리가 할당된 채로 존재
→ Static을 남발할 시, 시스템 성능 ↓

3. 사용처

1) 상수 정의

  • 해당 상수는 객체 내에서 매번 일반 변수로 정의하기보다는, 한 번 정적 변수로 정의하면 메모리를 아낄 수 있다.
    • (상수 : 절대 변하지 않는 변수)

2) 유틸리티 클래스 정의

  • java에서 Math 클래스를 보면, 상수 외에 인스턴스 변수가 하나도 없고 계산을 위한 정적 메소드만 제공한다.
    • 이렇게 애초부터 객체의 상태를 이용할 생각이 없고, 여러 객체들의 필요에 의해 데이터를 처리하는 공통 로직이 필요할 때는 static을 사용하여 설계한다.
      • (유틸리티 클래스 : 인스턴스 메소드와 인스턴스 변수를 제공하지 않고, 데이터 처리를 위한 정적 메소드만 존재하는 클래스)

3. 문제점

1) 메모리

  • static 은 프로그램 실행 시점에 메모리에 할당을 하며, 웬만하면 프로그램 종료 시점까지 메모리에서 해제되지 않는다.

2) 동시성 이슈

  • static 은 전역에서 접근이 가능하므로, 별도의 동기화 전략을 수립해야 한다.

3) 런타임 다형성 불가

  • static으로만 이루어진 메소드를 사용하는 객체의 경우, 해당 객체를 메모리로 할당하여 사용하는 것이 아니고 객체.메소드로 바로 접근하여 호출한다.

4) 객체의 상태를 이용할 수 없다

  • 정적 메소드 안에는 클래스의 인스턴스 필드를 사용할 수 없기 때문에,
    정적 메소드를 사용하기 위해서는 필요로 하는 인자를 모두 외부에서 주입해야 한다.
    • static 은 프로그램 시점에 메모리에 올라가는데, 정적 메소드 안에 객체의 인스턴스 필드가 초기화되지 않았다면 문제가 생길 수 있다.
    • 그래서 정적 메소드 안에는 정적 변수만 사용할 수 있다.
  • 일반 메소드라면 객체 내의 있는 상태를 통해 해당 메소드를 구현해 줄 수 있으므로, 변화하는 상태에 따라 다채로운 기능 구현이 가능하다.
    → 이는 결국 객체 내의 정적 메소드가 많아지면, 외부 값에 의존하는 수동적인 객체가 되어 버린다.

5) 테스트 어려움

  • 정적 필드는 전역으로 관리되므로, 프로그램 전체에서 이 필드에 접근하고 수정할 수 있다.
    • 따라서 해당 필드를 추론하기 어려워 테스트하기 까다롭다.

4. 사용법

Static영역을 활용하고자 한다면,
객체 생성을 위해 생성자보다는 정적 팩토리 메서드를 사용해야한다.

참고: 정적 팩토리 메서드

(1) 정적 멤버 생성

// 정적 필드
  // 타입 필드 = 초기값
static int num = 0; 

// 정적 메소드
  // static 리턴 타입 메소드 {}
public static void static_method(){} 

필드/메소드 생성 시, 인스턴스로 생성할것인지 정적으로 생성할것인지에 대한 판단 기준은 공용으로 사용하느냐 아니냐로 내리면 된다.

그냥 생성한다면, 자동으로 인스턴스로 생성되며
정적으로 생성하려면, 필드와 메소드 선언 시 static이라는 키워들를 추가적으로 붙이면 된다.

(2) 정적 필드

// 1. Number 클래스 안에 클래스 변수 num1, 인스턴스 변수 num2 를 생성
class Number{
    static int num1 = 0; 	// 클래스 필드
    int num2 = 0; 	// 인스턴스 필드
}

public class Static_ex {
	
    // 2. Number인스턴스인 number1, number2 를 생성했을 때,
    public static void main(String[] args) {
    	Number number1 = new Number(); 		// 첫번째 number
    	Number number2 = new Number(); 		// 두번쨰 number
    	
        // 3. number1에서 num1과 num2를 각각 1씩 증가
    	number1.num1++; 	// 클래스 필드 num 을 1증가시킴
    	number1.num2++; 	// 인스턴스 필드 num 을 1증가시킴
        
        // 4. number2 에서 num1, num2 를 각각 출력시켰을 때, num1 은 1, num2 는 0 을 출력
    	System.out.println(number2.num1); 		// 두번째 number의 클래스 필드 출력
    	System.out.println(number2.num2); 		// 두번째 number의 인스턴스 필드 출력
    }
}

num1 은 1, num2 는 0 을 출력된 이유
인스턴스 변수는 인스턴스가 생성될 때마다 생성되므로, 인스턴스마다 각기 다른 값을 가지지만(num2)
정적 변수는 모든 인스턴스가 하나의 저장공간을 공유하기에 항상 같은 값을 가지기 때문이다.(num1)

(3) 정적 메서드

① 정의

  • 클래스 메서드라고도 부른다

  • 인스턴스 멤버(인스턴스 변수, 인스턴스 메서드) 와 관련없는 작업을 하는 메서드

  • 메서드 내에서 인스턴스 변수 사용 불가능

  • 객체 생성 없이 '클래스이름.메서드이름()'으로 호출
    ex) Math.random()

② 예시 : 정적 메서드 vs 인스턴스 메서드

  1. class Name{
    
        // 클래스 메소드
        static void print1() { 	
        System.out.println("내 이름은 홍길동입니다.");
        }
    
        // 인스턴스 메소드
        void print2() { 	
        System.out.println("내 이름은 이순신입니다.");
        }
    }
    
    public class Static_ex {
    
        public static void main(String[] args) {
            Name.print1(); 		// 인스턴스를 생성하지 않아도 호출이 가능
    
            Name name = new Name(); 	// 인스턴스 생성
            name.print2(); 		// 인스턴스를 생성하여야만 호출이 가능
        }
    }
    
    /* 출력결과
    내 이름은 홍길동입니다.
    내 이름은 이순신입니다.
    */
    • 정적 메소드는 클래스가 메모리에 올라갈 때, 자동 생성된다.
      • 따라서, 정적 메소드는 인스턴스를 생성하지 않아도 호출이 가능하다.
      • 정적 메소드는 유틸리티 함수를 만드는데 유용하게 사용
        • (유틸리티 함수 : 여러 가지의 계산과 처리를 대신해주는 라이브러리 함수)
  2. // 인스턴스 변수의 경우
    class StaticMethod7_1 {
        int num1, num2; // 인스턴스 변수 선언
    
        static int add(int num1, int num2) { // 메서드 반환타입 앞에 static 이 붙었기 때문에 static 메서드가 됩니다.
    
            System.out.println();
            System.out.println("매개변수로 입력 받은 num1, num2 확인");
            System.out.println("num1 = " + num1);
            System.out.println("num2 = " + num2);
    
            // 여기서 매개 변수에 있는 num1, num2 와 위에 인스턴스 변수로 선언한 num1, num2 는 같은 걸까요?
            // 기본형 매개변수에서 배웠듯이 같지 않습니다. 서로 영향을 받지 않습니다.
    
            // 여기서의 num1 과 num2 는 단지 인스턴스 변수와 이름만 같은 지역변수일 뿐 메서드가 종료되면 사라집니다.
            // 인스턴스 변수는 클래스 메서드에서 사용하지 못합니다.
        }    
    }    
    
    // 클래스 변수의 경우
    class StaticMethod7_1 {
    
        // 클래스 변수 선언
        static int num1 = 10, num2 = 20;
    
        static int add(int num1, int num2) { // 메서드 반환타입 앞에 static 이 붙었기 때문에 static 메서드가 됩니다.
    
            System.out.println();
            System.out.println("매개변수로 입력 받은 num1, num2 확인");
            System.out.println("num1 = " + num1);
            System.out.println("num2 = " + num2);
    
            // 추가로 StaticMethod7_1 에 선언한 변수를 사용하고 싶다면?
            // 인스턴스 변수가 아닌 클래스 변수를 선언해야합니다. static 타입 변수이름;
    
            // 자 그럼 이제 클래스 변수로 선언을 했으니까
            // 매개 변수에 있는 num1, num2 와 위에 클래스 변수로 선언한 num1, num2 는 같은 걸까요?
            // 그렇지 않습니다. 위에서 말씀드렸듯이 num1, num2는 매개변수이자 클래스 변수와 이름만 같습니다.
            // 또한 클래스 변수 를 사용하려면 클래스명.클래스변수 로 만 접근 가능합니다.
    
            // 이렇게 클래스 메서드에서 클래스 변수를 사용할 수 있다.
            System.out.println("클래스변수 확인");
            System.out.println("StaticMethod7_1.num1 = " + StaticMethod7_1.num1);
            System.out.println("StaticMethod7_1.num2 = " + StaticMethod7_1.num2);
    
            System.out.println("num1 + num2 = " + (num1 + num2));
            return num1 + num2;
        }
    }
    
    class StaticMethod7_1Main {
    
        public static void main(String[] args) {
            StaticMethod7_1 methodTest = new StaticMethod7_1(); // 객체 생성
            methodTest.num1 = 10;
            methodTest.num2 = 20;
    
            // methodTest.add(methodTest.num1, methodTest.num2); // add 는 static 메서드 이기 때문에 객체로 생성된 참조변수로는 접근할 수 없습니다.
    
            // 클래스이름.메서드이름() 방식으로 static 메서드를 호출
            StaticMethod7_1.add(methodTest.num1, methodTest.num2);
        }
    }

(4) static final

참고: final

5. 싱글톤 패턴

class Singleton {
    private static Singleton one;		// static 변수인 one
    private Singleton() {
    }

	// getInstance() 메서드에서 one 값이 null 인 경우에만 객체를 생성하도록 하여, one 객체가 딱 한 번만 만들어지도록 했다.
    public static Singleton getInstance() {
        if(one==null) {
            one = new Singleton();
        }
        return one;
    }
}

public class Sample {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2);  // true 출력
    }
}
  1. 최초 getInstance() 를 호출
  2. one 이 null 이므로, new 에 의해서 객체가 생성이 된다.
  3. one 은 static 변수이므로, 그 이후로는 null이 아니게 된다. → 정적 변수는 하나의 저장공간을 공유하기 때문
  4. 다시 getInstance() 를 호출하면, 이제 one은 null이 아니므로 이미 만들어진 싱글톤 객체인 one 을 항상 리턴

참고: 디자인 패턴 - (2) 생성 패턴 - 5) 싱글톤 (Singleton)

profile
개발자로 거듭나기!

0개의 댓글