[Java] 개념노트 24 static 키워드 이해하기

[Java 개념노트 24] static 키워드 이해하기

안녕하세요. Java 개념노트 시리즈 스물네 번째 글입니다.

지난 글에서는 현재 객체 자기 자신을 가리키는 this 키워드에 대해 정리했습니다. 이번 글에서는 객체지향을 공부할 때 자주 헷갈리는 static 키워드에 대해 알아보겠습니다.

static은 자바를 처음 배울 때부터 자주 등장합니다. 대표적으로 public static void main(String[] args)에서 이미 본 적이 있습니다. 그런데 정확히 static이 무엇인지 모르고 넘어가면 클래스와 객체를 배울 때 계속 헷갈릴 수 있습니다.


1. static이란?

static은 클래스에 소속되는 멤버를 만들 때 사용하는 키워드입니다.

일반 필드와 일반 메서드는 객체를 생성한 뒤 객체를 통해 사용합니다. 하지만 static이 붙은 필드나 메서드는 객체를 만들지 않아도 클래스 이름으로 바로 사용할 수 있습니다.

쉽게 말하면
static은 객체마다 따로 가지는 것이 아니라, 클래스가 함께 공유하는 값이나 기능을 만들 때 사용합니다.

2. 인스턴스 멤버와 static 멤버

클래스 안에 있는 필드와 메서드는 크게 두 가지로 나눌 수 있습니다.

  • 인스턴스 멤버 : 객체를 생성해야 사용할 수 있는 필드와 메서드
  • static 멤버 : 객체를 생성하지 않아도 클래스 이름으로 사용할 수 있는 필드와 메서드
구분 소속 사용 방법
인스턴스 필드 객체 객체명.필드명
인스턴스 메서드 객체 객체명.메서드명()
static 필드 클래스 클래스명.필드명
static 메서드 클래스 클래스명.메서드명()

3. 인스턴스 필드 예제

먼저 static이 없는 일반 필드를 보겠습니다. 일반 필드는 객체마다 따로 값을 가집니다.

public class Main {
    public static void main(String[] args) {
        Student student1 = new Student();
        student1.name = "건";

        Student student2 = new Student();
        student2.name = "자바";

        System.out.println(student1.name);
        System.out.println(student2.name);
    }
}

class Student {
    String name;
}

실행 결과는 다음과 같습니다.

건
자바

student1student2는 각각 다른 객체입니다. 따라서 name 값도 객체마다 따로 저장됩니다.

4. static 필드 예제

이번에는 static 필드를 사용해보겠습니다. static 필드는 객체마다 따로 존재하는 것이 아니라 클래스가 하나만 가지고 공유합니다.

public class Main {
    public static void main(String[] args) {
        Student.schoolName = "GWDEVEL 고등학교";

        Student student1 = new Student();
        student1.name = "건";

        Student student2 = new Student();
        student2.name = "자바";

        System.out.println(student1.name);
        System.out.println(student2.name);
        System.out.println(Student.schoolName);
    }
}

class Student {
    String name;
    static String schoolName;
}

실행 결과는 다음과 같습니다.

건
자바
GWDEVEL 고등학교

name은 학생마다 다르므로 인스턴스 필드로 만들었습니다. 반면 schoolName은 모든 학생이 같은 학교명을 공유할 수 있으므로 static 필드로 만들었습니다.

핵심 포인트
객체마다 달라야 하는 값은 인스턴스 필드, 모든 객체가 함께 공유해야 하는 값은 static 필드로 만들 수 있습니다.

5. static 필드는 클래스 이름으로 접근하기

static 필드는 객체를 통해서도 접근할 수는 있지만, 권장 방식은 클래스 이름으로 접근하는 것입니다.

권장하는 방식

Student.schoolName = "GWDEVEL 고등학교";
System.out.println(Student.schoolName);

권장하지 않는 방식

Student student = new Student();
student.schoolName = "GWDEVEL 고등학교";

static 멤버는 객체의 것이 아니라 클래스의 것이므로 클래스명.멤버명 형태로 접근하는 것이 좋습니다.

6. static 메서드란?

static 메서드는 객체를 생성하지 않고 클래스 이름으로 바로 호출할 수 있는 메서드입니다.

public class Main {
    public static void main(String[] args) {
        Calculator.printHello();

        int result = Calculator.add(10, 20);
        System.out.println(result);
    }
}

class Calculator {
    static void printHello() {
        System.out.println("계산기입니다.");
    }

    static int add(int a, int b) {
        return a + b;
    }
}

실행 결과는 다음과 같습니다.

계산기입니다.
30

Calculator 객체를 만들지 않았지만 Calculator.add(10, 20)처럼 클래스 이름으로 메서드를 호출했습니다.

7. static 메서드는 언제 사용할까?

static 메서드는 객체의 상태와 관계없이 동작하는 기능을 만들 때 사용합니다.

예를 들어 두 숫자를 더하는 기능은 특정 객체의 필드 값을 사용하지 않아도 됩니다. 이런 단순 계산 기능은 static 메서드로 만들기 좋습니다.

class MathUtil {
    static int square(int number) {
        return number * number;
    }

    static int max(int a, int b) {
        return a > b ? a : b;
    }
}

호출은 다음처럼 할 수 있습니다.

System.out.println(MathUtil.square(5));
System.out.println(MathUtil.max(10, 20));

8. static 메서드에서는 this를 사용할 수 없다

지난 글에서 this는 현재 객체 자기 자신을 가리킨다고 배웠습니다. 그런데 static 메서드는 객체 없이도 호출할 수 있습니다. 그래서 static 메서드 안에서는 this를 사용할 수 없습니다.

class Student {
    String name;

    static void printName() {
        System.out.println(this.name);
    }
}

위 코드는 오류가 발생합니다. static 메서드는 특정 객체에 속한 메서드가 아니기 때문에 this가 존재하지 않습니다.

주의
this는 객체가 있어야 사용할 수 있습니다. static 메서드는 객체 없이 호출될 수 있으므로 this를 사용할 수 없습니다.

9. static 메서드에서 인스턴스 필드를 바로 사용할 수 없다

static 메서드에서는 인스턴스 필드를 바로 사용할 수 없습니다. 인스턴스 필드는 객체를 생성해야 존재하기 때문입니다.

class Student {
    String name;

    static void printName() {
        System.out.println(name);
    }
}

위 코드는 오류가 발생합니다. name은 객체마다 따로 존재하는 인스턴스 필드인데, static 메서드는 어떤 객체의 name을 말하는지 알 수 없습니다.

10. static 메서드에서 인스턴스 멤버를 사용하려면?

static 메서드 안에서 인스턴스 필드나 인스턴스 메서드를 사용하려면 객체를 직접 생성하거나 전달받아야 합니다.

public class Main {
    public static void main(String[] args) {
        Student student = new Student("건");

        Student.printStudentName(student);
    }
}

class Student {
    String name;

    Student(String name) {
        this.name = name;
    }

    static void printStudentName(Student student) {
        System.out.println(student.name);
    }
}

실행 결과는 다음과 같습니다.

static 메서드가 직접 name을 사용하는 것이 아니라, 전달받은 student 객체를 통해 student.name에 접근했습니다.

11. 인스턴스 메서드에서는 static 멤버를 사용할 수 있다

반대로 인스턴스 메서드에서는 static 필드와 static 메서드를 사용할 수 있습니다. static 멤버는 클래스가 가지고 있으므로 객체에서도 접근할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        Student.schoolName = "GWDEVEL 고등학교";

        Student student = new Student("건");
        student.printInfo();
    }
}

class Student {
    static String schoolName;
    String name;

    Student(String name) {
        this.name = name;
    }

    void printInfo() {
        System.out.println("학교: " + schoolName);
        System.out.println("이름: " + name);
    }
}

실행 결과는 다음과 같습니다.

학교: GWDEVEL 고등학교
이름: 건

12. static 필드는 공유된다

static 필드는 모든 객체가 함께 공유합니다. 따라서 한 곳에서 static 필드 값을 변경하면 다른 객체에서도 변경된 값을 볼 수 있습니다.

public class Main {
    public static void main(String[] args) {
        Student student1 = new Student("건");
        Student student2 = new Student("자바");

        Student.schoolName = "A학교";
        student1.printInfo();
        student2.printInfo();

        Student.schoolName = "B학교";
        student1.printInfo();
        student2.printInfo();
    }
}

class Student {
    static String schoolName;
    String name;

    Student(String name) {
        this.name = name;
    }

    void printInfo() {
        System.out.println(name + " / " + schoolName);
    }
}

실행 결과는 다음과 같습니다.

건 / A학교
자바 / A학교
건 / B학교
자바 / B학교

schoolName은 static 필드이기 때문에 student1student2가 같은 값을 공유합니다.

13. static을 잘못 사용하면 생기는 문제

static은 공유되는 값이기 때문에 객체마다 달라야 하는 값을 static으로 만들면 문제가 생길 수 있습니다.

class Student {
    static String name;
}

학생마다 이름은 달라야 합니다. 그런데 name을 static으로 만들면 모든 학생 객체가 하나의 이름 값을 공유하게 됩니다. 이런 경우는 잘못된 설계입니다.

Student student1 = new Student();
Student student2 = new Student();

student1.name = "건";
student2.name = "자바";

System.out.println(student1.name);
System.out.println(student2.name);

위 코드에서는 두 객체가 같은 static 필드 name을 공유하기 때문에 의도와 다른 결과가 나올 수 있습니다.

기억하기
객체마다 달라야 하는 값은 static으로 만들면 안 됩니다. 모든 객체가 함께 공유해야 하는 값에만 static을 사용하는 것이 좋습니다.

14. static final 상수

staticfinal과 함께 사용해서 상수를 만들 때 자주 사용됩니다. 상수는 한 번 정하면 바뀌지 않는 값입니다.

class MathConstants {
    static final double PI = 3.141592;
}

상수는 보통 대문자와 밑줄을 사용해서 이름을 작성합니다.

System.out.println(MathConstants.PI);

PI는 모든 곳에서 같은 값으로 사용되며, 변경되면 안 되는 값입니다. 이런 값은 static final로 만들기 좋습니다.

15. static 초기화 블록

자바에는 static 필드를 초기화할 때 사용하는 static 초기화 블록도 있습니다.

class AppConfig {
    static String appName;

    static {
        appName = "GWDEVELBlog App";
        System.out.println("static 초기화 블록 실행");
    }
}

static 초기화 블록은 클래스가 처음 사용될 때 한 번 실행됩니다. 처음 공부할 때는 자주 쓰지는 않지만, static 필드에 복잡한 초기 설정이 필요할 때 사용할 수 있습니다.

16. main 메서드가 static인 이유

자바 프로그램은 main 메서드에서 시작합니다.

public static void main(String[] args) {
    System.out.println("Hello Java");
}

main 메서드가 static인 이유는 프로그램 시작 시점에 아직 객체가 생성되어 있지 않기 때문입니다. 객체를 만들기 전에도 JVM이 바로 호출할 수 있어야 하므로 main 메서드는 static으로 작성됩니다.

핵심 포인트
JVM은 객체를 만들지 않고도 프로그램을 시작해야 하므로 main 메서드를 static으로 호출합니다.

17. static 사용 기준

처음에는 어떤 경우에 static을 써야 할지 헷갈릴 수 있습니다. 아래 기준으로 생각하면 좋습니다.

상황 static 사용 여부 예시
객체마다 달라야 하는 값 사용하지 않음 학생 이름, 나이, 점수
모든 객체가 공유해야 하는 값 사용 가능 학교명, 전체 학생 수
객체 상태와 관계없는 기능 사용 가능 수학 계산 유틸 메서드
변하지 않는 공통 상수 static final 사용 PI, MAX_SIZE

18. static 전체 예제

이번에는 static 필드와 인스턴스 필드를 함께 사용하는 전체 예제를 보겠습니다.

public class Main {
    public static void main(String[] args) {
        Student.schoolName = "GWDEVEL 고등학교";

        Student student1 = new Student("건", 90);
        Student student2 = new Student("자바", 85);

        student1.printInfo();
        student2.printInfo();

        System.out.println("학생 수: " + Student.count);
    }
}

class Student {
    static String schoolName;
    static int count = 0;

    String name;
    int score;

    Student(String name, int score) {
        this.name = name;
        this.score = score;
        count++;
    }

    void printInfo() {
        System.out.println("학교: " + schoolName + ", 이름: " + name + ", 점수: " + score);
    }
}

실행 결과는 다음과 같습니다.

학교: GWDEVEL 고등학교, 이름: 건, 점수: 90
학교: GWDEVEL 고등학교, 이름: 자바, 점수: 85
학생 수: 2

schoolNamecount는 모든 학생 객체가 공유하는 static 필드입니다. namescore는 객체마다 다른 인스턴스 필드입니다.

19. static 사용 시 자주 하는 실수

static을 처음 배울 때는 아래와 같은 실수를 자주 합니다.

실수 문제점 해결 방법
객체마다 달라야 하는 값을 static으로 만듦 모든 객체가 값을 공유해버림 이름, 나이, 점수 등은 인스턴스 필드로 작성
static 메서드에서 this 사용 static에는 현재 객체가 없음 this는 인스턴스 메서드에서 사용
static 메서드에서 인스턴스 필드 바로 사용 어떤 객체의 필드인지 알 수 없음 객체를 생성하거나 전달받아서 사용
static 멤버를 객체명으로 접근 클래스 소속이라는 의미가 흐려짐 클래스명.멤버명으로 접근
모든 메서드에 무조건 static을 붙임 객체지향 설계가 흐려질 수 있음 객체 상태를 사용하는 메서드는 인스턴스 메서드로 작성

20. 직접 연습해보기

아래 코드를 직접 작성하고 실행해보세요.

public class Main {
    public static void main(String[] args) {
        Product.storeName = "GWDEVEL Store";

        Product product1 = new Product("키보드", 50000);
        Product product2 = new Product("마우스", 30000);

        product1.printInfo();
        product2.printInfo();

        System.out.println("등록 상품 수: " + Product.productCount);
        System.out.println("할인 가격: " + Product.discountPrice(50000, 5000));
    }
}

class Product {
    static String storeName;
    static int productCount = 0;

    String name;
    int price;

    Product(String name, int price) {
        this.name = name;
        this.price = price;
        productCount++;
    }

    void printInfo() {
        System.out.println("상점: " + storeName + ", 상품명: " + name + ", 가격: " + price);
    }

    static int discountPrice(int price, int discount) {
        return price - discount;
    }
}

실행 결과는 다음과 같습니다.

상점: GWDEVEL Store, 상품명: 키보드, 가격: 50000
상점: GWDEVEL Store, 상품명: 마우스, 가격: 30000
등록 상품 수: 2
할인 가격: 45000

21. 이번 글 정리

이번 글에서는 클래스가 공유하는 멤버를 만들 때 사용하는 static 키워드에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.

  • static은 클래스에 소속되는 멤버를 만들 때 사용한다.
  • 인스턴스 멤버는 객체를 생성해야 사용할 수 있다.
  • static 멤버는 객체 생성 없이 클래스 이름으로 사용할 수 있다.
  • 객체마다 달라야 하는 값은 인스턴스 필드로 만든다.
  • 모든 객체가 공유해야 하는 값은 static 필드로 만들 수 있다.
  • static 메서드는 객체 상태와 관계없는 기능에 어울린다.
  • static 메서드에서는 this를 사용할 수 없다.
  • static 메서드에서는 인스턴스 필드를 바로 사용할 수 없다.
  • 공통 상수는 static final로 자주 만든다.
  • main 메서드는 객체 생성 없이 JVM이 호출해야 하므로 static이다.

한 줄 요약
static은 객체마다 따로 가지는 것이 아니라 클래스가 함께 공유하는 값과 기능을 만들 때 사용하는 키워드입니다.

다음 글 예고
다음 글에서는 [Java 개념노트 25] final 키워드 이해하기라는 주제로 값을 변경하지 못하게 막는 방법과 상수 개념을 정리해보겠습니다.

GWDEVELBlog Java 개념노트 시리즈