개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Java › 클래스와 객체

static과 인스턴스

b만 건드렸는데 왜 a의 값도 바뀌나

필드 앞에 static을 붙이면 그 필드는 객체가 아니라 클래스에 하나만 생기고, 모든 객체가 그 하나를 함께 씁니다. static을 붙이지 않은 인스턴스 필드는 객체마다 따로 생깁니다. static 메서드는 객체 없이 불리므로, 그 안에서는 인스턴스 필드와 this를 쓸 수 없습니다.

실행으로 대조 · JDK 21에서 서른 가지 조합을 컴파일하고 실행해 컴파일 결과와 출력을 대조

이 실습에 나오는 개념

static
인스턴스가 아니라 클래스에 속하는 멤버임을 나타내는 키워드다. static 필드는 클래스당 하나만 존재해 모든 인스턴스가 공유하고, static 메서드는 객체를 만들지 않고 호출한다. 값이 바뀌는 static 필드는 사실상 전역 변수이므로 여러 스레드가 동시에 접근할 때 문제가 되기 쉽다.
클래스와 객체
클래스는 어떤 필드(데이터)와 메서드(동작)를 가질지 적어 둔 설계도이고, 객체는 그 설계도로 new를 해서 메모리(힙)에 실제로 만든 것이다. 객체를 인스턴스라고도 부른다. 클래스 하나로 객체를 여러 개 만들 수 있고, 객체마다 필드 값을 따로 가진다. 변수에는 객체 자체가 아니라 객체가 있는 곳을 가리키는 참조가 담긴다.
인스턴스 멤버와 static 멤버
인스턴스 멤버는 static을 붙이지 않은 필드와 메서드로, 객체에 속한다. 인스턴스 필드는 객체마다 따로 생기고, 인스턴스 메서드는 a.hit()처럼 객체를 대상으로 불러야 한다. static 멤버는 클래스에 속해서 객체 없이 클래스이름.멤버로 쓴다. static 메서드 안에는 대상 객체가 없으므로 인스턴스 필드, 인스턴스 메서드, this를 이름만으로 쓸 수 없고, 쓰면 컴파일 오류가 난다. 반대로 인스턴스 메서드 안에서는 static 멤버를 자유롭게 쓸 수 있다.
this
인스턴스 메서드나 생성자 안에서 지금 다루고 있는 객체 자신을 가리키는 참조다. this.name처럼 써서 같은 이름의 매개변수와 필드를 구분한다. 생성자의 첫 문장으로 this(인자)를 쓰면 같은 클래스의 다른 생성자를 먼저 실행한다. static 메서드에는 대상 객체가 없으므로 this를 쓸 수 없다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. 컴파일: Counter의 선언을 읽습니다. count는 static 필드라서 클래스에 하나만 있고, hit()은 static이 붙지 않은 인스턴스 메서드라서 a.hit()처럼 객체를 대상으로 부릅니다.
  2. 실행 시작: Counter 클래스가 처음 쓰일 때 클래스가 준비됩니다. static 필드 count가 클래스에 하나 만들어지고 기본값 0이 들어갑니다. 아직 객체는 하나도 없습니다.
  3. Counter a = new Counter(): 힙에 객체가 하나 만들어지고 a가 그것을 가리킵니다. count는 static이라 객체 안에 들어가지 않습니다. 이 객체에는 자기만의 count가 없습니다.
  4. Counter b = new Counter(): 두 번째 객체가 만들어지고 b가 그것을 가리킵니다. b에도 자기만의 count는 없습니다. count는 여전히 클래스에 하나뿐입니다.
  5. a.hit(): hit() 안의 count++가 실행됩니다. count는 static 필드이므로 클래스에 하나뿐인 count가 1 늘어납니다. 이제 값은 1입니다.
  6. a.hit(): hit() 안의 count++가 실행됩니다. count는 static 필드이므로 클래스에 하나뿐인 count가 1 늘어납니다. 이제 값은 2입니다.
  7. b.hit(): hit() 안의 count++가 실행됩니다. b를 대상으로 불렀지만 count는 static 필드이므로, a.hit()이 올려 둔 바로 그 count가 이어서 올라갑니다. 이제 값은 3입니다.
  8. System.out.println(a.count + " " + b.count): a.count와 b.count는 적힌 모양만 다를 뿐 둘 다 클래스에 하나뿐인 count를 읽습니다. 객체 변수로 static 필드를 읽으면 변수의 타입인 Counter의 static 필드가 읽힙니다. 출력은 3 3입니다.

실행 결과

코드

class Counter {
    static int count;              // static 필드: 클래스에 하나

    void hit() {                   // 인스턴스 메서드
        count++;
    }
}

Counter a = new Counter();
Counter b = new Counter();
a.hit();
a.hit();
b.hit();
System.out.println(a.count + " " + b.count);

이 모형의 전제

서른 가지 조합을 이 PC의 JDK 21에서 같은 코드로 실제로 컴파일하고 실행해, 컴파일 결과와 오류 메시지, 출력을 대조했습니다. 요약에서 고치는 방법으로 든 "객체를 인자로 받는 static 메서드"가 컴파일되고 실행된다는 것, 객체 변수로 static 필드를 읽을 때 객체가 쓰이지 않는다는 것(null인 변수로 읽어도 예외가 나지 않음)도 실행으로 확인했습니다. 단순화한 점: 그림의 "클래스" 영역은 JVM이 클래스 정보를 두는 곳을 뭉뚱그린 것이며, static 필드가 메모리의 정확히 어디에 놓이는지는 JVM 구현에 따라 다릅니다. 클래스가 준비되는 정확한 시점(처음으로 객체를 만들거나 static 멤버를 쓸 때)은 "실행 시작" 한 단계로 줄였습니다. a.count처럼 객체 변수로 static 필드를 쓰는 코드는 컴파일되지만 권장되지 않으며, 공유된다는 것을 보이려고 넣었습니다. 오류 메시지는 javac의 영어 메시지 첫 줄입니다.

앞 실습: 클래스, 객체, 생성자 · 다음 실습: 상속과 오버라이딩

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요