[java] java static 멤버와 static 메서드

Goal

  • non-static 멤버와 static 멤버의 차이를 이해할 수 있다.
  • static 멤버의 사용법을 이해할 수 있다.
  • static의 활용과 static 메서드의 제약 조건을 이해할 수 있다.

static 멤버의 선언

멤버 선언 시 앞에 static이라고 붙인다.

class StaticSample {
  int n; // non-static 필드
  void g() {...} // non-static 메서드

  static int m; // static 필드
  static void f() {...} // static 메서드
}

non-static 멤버 VS static 멤버

non-static 멤버

  • 공간적 특성: 멤버는 객체마다 별도로 존재한다.
    • 인스턴스 멤버 라고 부른다.
  • 시간적 특성: 객체 생성 시에 멤버가 생성된다.
    • 객체가 생길 때 멤버도 생성된다.
    • 객체 생성 후 멤버 사용이 가능하다.
    • 객체가 사라지면 멤버도 사라진다.
  • 공유의 특성: 공유되지 않는다.
    • 멤버는 객체 내에 각각의 공간을 유지한다.

static 멤버

  • 공간적 특성: 멤버는 클래스당 하나가 생성된다.
    • 멤버는 객체 내부가 아닌 별도의 공간에 생성된다.
    • 클래스 멤버 라고 부른다.
  • 시간적 특성: 클래스 로딩 시에 멤버가 생성된다.
    • 객체가 생기기 전에 이미 생성된다.
    • 객체가 생기기 전에도 사용이 가능하다. (즉, 객체를 생성하지 않고도 사용할 수 있다.)
    • 객체가 사라져도 멤버는 사라지지 않는다.
    • 멤버는 프로그램이 종료될 때 사라진다.
  • 공유의 특성: 동일한 클래스의 모든 객체들에 의해 공유된다.

static 멤버의 사용법

1. 객체.static 멤버

static 멤버도 역시 멤버이기 때문에 일반적인 멤버 사용법과 다를 바 없다.

객체.static 멤버
객체.static 메서드
  • static 필드는 클래스의 모든 객체에 공통으로 사용되는 변수가 된다.
  • C/C++의 전역 변수(global variable)와 유사하다고 볼 수 있다.
  • 클래스의 어떤 멤버도 static 멤버를 접근할 수 있다.

static 멤버의 생성과 공유

class StaticSample {
  public int n;
  public void g() { m = 20; }
  public void h() { m = 30; }
  public static int m;
  public static void f() { m = 5; }
}
  • static 멤버가 생성되는 시점은 main() 메서드가 실행을 시작한 후 StaticSample이 등장하는 시점이다.
  • 다음 코드가 실행되기 이전부터 static 멤버 m과 f()는 이미 존재하며 사용이 가능하다.
public class Main {
  public static void main(String args[]) {
    StaticSample s1, s2;
    s1 = new StaticSample();
    s1.n = 5;
    s1.g();
    s1.m = 50; // static

    s2 = new StaticSample();
    s2.n = 8;
    s2.h();
    s2.f(); // static
    System.out.println(s1.m); // result: 5
  }
}
  • static 멤버 m과 f()는 객체 s1, s2가 생성되기 이전에 이미 생성되어 있으며,
    • s1, s2가 생성될 때 static 멤버가 별도로 생성되는 것은 아니다.
  • s1 = new StaticSample();와 s2 = new StaticSample();가 실행되면,
    • 각 객체마다(s1, s2) 인스턴스 멤버인 n, g(), h()가 생성된다.
  • 객체 s1, s2는 static 멤버를 공유하고 있다.
    • 즉, s1, s2 모두 자신의 멤버라고 생각하여 g(), h() 메서드에서 static 멤버 m을 공유하여 사용하고 있다.

2. 클래스명.static 멤버

static 멤버는 클래스당 하나만 있기 때문에 클래스 이름으로 바로 접근할 수 있다.

클래스명.static 멤버
  • new 에 의해 객체가 생기기 전에 static 멤버에 접근할 수 있다.

static 멤버의 생성과 공유

class StaticSample {
  위와 동일
}
public class Main {
  public static void main(String args[]) {
    StaticSample.m = 10;

    StaticSample s1;
    s1 = new StaticSample();
    System.out.println(s1.m); // result: 10

    /* static 메서드 사용 */
    s1.f(); // 1. 객체 레퍼런스로 static 멤버 f() 호출
    StaticSample.f(); // 2. 클래스명을 이용하여 static 멤버 f() 호출
  }
}
  • StaticSample.h();, StaticSample.g();
    • h(), g() 메서드는 non-static 이므로 오류

주의

  • 실제 static 멤버의 생성 시점은 JVM(자바 가상 기계)에 따라 다르다.
  • 그러나 일반적으로 static 멤버가 포함된 클래스가 로딩하는 시점에 static 멤버가 생성된다고 볼 수 있다.
  • JVM은 많은 경우 처음부터 필요한 대부분의 클래스를 로딩하기 때문에 static 멤버의 생성 시점은 JVM이 시작되는 시점이라고 할 수 있다.

static의 활용

1. 전역 변수와 전역 함수를 만들 때 활용

  • 캡슐화 원칙
    • 자바에서는 C/C++와 달리 어떤 변수나 함수도 클래스 바깥에 존재할 수 없으며 클래스의 멤버로 존재하여야 한다.
  • 그러나 응용프로그램 작성 시 모든 클래스에서 공유하는 전역 변수(global variable)나 모든 클래스에서 언제든지 호출할 수 있는 전역 함수(global function)를 만들어 사용할 필요가 생긴다.
    • static은 이런 문제의 해결책이다.
    • Ex) java.lang.Math 클래스
      • JDK와 함께 배포되는 클래스
      • 객체를 생성하지 않고 바로 호출할 수 있는 수학 계산용 상수와 메서드를 제공
        public class Math {
        public static int abs(int a);
        public static double cos(double a);
        ... // 모든 멤버가 static
        }
        
        /* 잘못된 사용법 */
        Math m = new Math(); // 현재 생성자 Math()는 private으로 선언되어 있어 객체 생성 안 됨
        int n = m.abs(-5);
        /* 올바른 사용법 */
        int n = Math.abs(-5);
        

2. 공유 멤버를 만들고자 할 때 활용

static으로 선언된 필드나 메서드는 모두 이 클래스의 각 객체들의 공통 멤버가 되며 객체들 사이에서 공유된다.

static 메서드의 제약 조건

1. static 메서드는 오직 static 멤버만 접근할 수 있다.

static 메서드는 객체가 생성되지 않은 상황에서도 사용이 가능하므로 객체에 속한 인스턴스 메소드, 인스턴스 변수 등을 사용할 수 없다.

  • static 멤버들만 사용이 가능하다.
  • 그러나 인스턴스 메서드는 static 멤버들을 모두 사용할 수 있다.
class StaticMethod {
  int n;
  void f1(int x) { n = x; } // 정상
  void f2(int x) { n = x; } // 정상

  static int m;
  static void s1(int x) { n = x; } // 컴파일 오류. static 메서드는 non-static 필드 사용 불가
  static void s2(int x) { f1(3); } // 컴파일 오류. static 메서드는 non-static 메서드 사용 불가

  static void s3(int x) { m = x; } // 정상. static 메서드는 static 필드 사용 가능
  static void s4(int x) { s3(3); } // 정상. static 메서드는 static 메서드 호출 가능
}

2. static 메서드에서는 this 키워드를 사용할 수 없다.

this는 호출 당시 실행 중인 객체를 가리키는 레퍼런스이다.

  • 따라서 객체가 생성되지 않은 상황에서도 클래스 이름을 이용하여 호출이 가능한 static 메서드는 this를 사용할 수 없다.
class StaticAndThis {
  int n;
  static int m;
  void f1(int x) { this.n = x; } // 정상
  void f2(int x) { this.m = x; } // non-static 메서드에서는 static 멤버 접근 가능
  static void s1(int x) { this.n = x; } // 컴파일 오류. static 메서드는 this 사용 불가
}