[java] static 키워드 - 제약사항~

2025. 9. 20. 23:21·공부일기../Java

 

1. static 사용법과 제약사항

1-1. static 메서드의 규칙

public class StaticRules {
    int instanceVar = 100;
    static int staticVar = 200;
    
    // static 메서드의 제약사항
    public static void staticMethod() {
        // 가능: static 변수 사용
        staticVar++;
        
        // 가능: static 메서드 호출
        anotherStaticMethod();
        
        // 불가능: 인스턴스 변수 직접 사용
        // instanceVar++;  // 컴파일 에러!
        
        // 불가능: 인스턴스 메서드 직접 호출
        // instanceMethod();  // 컴파일 에러!
        
        // 해결방법: 객체 생성 후 사용
        StaticRules obj = new StaticRules();
        obj.instanceVar++;
        obj.instanceMethod();
    }
    
    // 인스턴스 메서드는 모든 것에 접근 가능
    public void instanceMethod() {
        instanceVar++;    // 가능
        staticVar++;      // 가능 (static은 어디서든 접근 가능)
    }
    
    public static void anotherStaticMethod() {
        System.out.println("Static method called");
    }
}

1-2. 왜 이런 제약이 있을까?

// static 메서드는 객체 없이 호출됨
StaticRules.staticMethod();  // 어떤 객체의 instanceVar를 써야 할까?

// 인스턴스 메서드는 특정 객체로 호출됨  
StaticRules obj = new StaticRules();
obj.instanceMethod();        // 이 obj의 instanceVar를 사용! 명확함

 

핵심 이유:

  • static 메서드는 클래스 레벨에서 동작
  • 특정 객체 없이 호출되므로 어떤 인스턴스의 변수를 써야 할지 모름
  • 컴파일러가 애매함을 방지하기 위해 제약을 둠

1-3. static 변수 vs 인스턴스 변수

public class Counter {
    static int totalCount = 0;    // 모든 객체가 공유
    int instanceCount = 0;        // 각 객체마다 독립적
    
    public Counter() {
        totalCount++;      // 전체 카운터 증가
        instanceCount++;   // 개별 카운터 증가
    }
    
    public void printCount() {
        System.out.println("전체: " + totalCount);
        System.out.println("개별: " + instanceCount);
    }
}

// 사용 예시
Counter c1 = new Counter();  // totalCount=1, instanceCount=1
Counter c2 = new Counter();  // totalCount=2, instanceCount=1
Counter c3 = new Counter();  // totalCount=3, instanceCount=1

c1.printCount();  // 전체: 3, 개별: 1
c2.printCount();  // 전체: 3, 개별: 1

 

 


 

2. 실행 순서와 생명주기

2-1. 프로그램 실행 순서

public class ExecutionOrder {
    static {
        System.out.println("1. Static 블록 실행");
    }
    
    {
        System.out.println("3. 인스턴스 블록 실행");
    }
    
    public ExecutionOrder() {
        System.out.println("4. 생성자 실행");
    }
    
    public static void main(String[] args) {
        System.out.println("2. main 메서드 시작");
        new ExecutionOrder();
        System.out.println("5. main 메서드 종료");
    }
}

// 출력 결과:
// 1. Static 블록 실행
// 2. main 메서드 시작  
// 3. 인스턴스 블록 실행
// 4. 생성자 실행
// 5. main 메서드 종료

2-2. static 블록의 특징

public class StaticBlockExample {
    static int value;
    static String config;
    
    // 복잡한 초기화는 static 블록에서
    static {
        value = calculateInitialValue();
        config = loadConfiguration();
        System.out.println("Static 초기화 완료");
    }
    
    private static int calculateInitialValue() {
        // 복잡한 계산 로직
        return 42;
    }
    
    private static String loadConfiguration() {
        // 설정 파일 로드 등
        return "production";
    }
}

 

static 블록 사용 시점:

  • 복잡한 static 변수 초기화
  • 외부 자원 로딩
  • 라이브러리 초기화
  • 클래스 로딩 시 한 번만 실행되어야 하는 작업

2-3. 변수별 생명주기 상세

public class DetailedLifeCycle {
    static int staticVar = 1;     // 클래스 로딩 시 생성
    int instanceVar = 2;          // 객체 생성 시 생성
    
    public void method() {
        int localVar = 3;         // 메서드 시작 시 생성
        
        if (true) {
            int blockVar = 4;     // 블록 시작 시 생성
        }                         // blockVar 소멸
        
        // localVar 사용 가능
        // blockVar 사용 불가 (스코프 벗어남)
    }                             // localVar 소멸
    
    // instanceVar는 객체 소멸(GC) 시까지 유지
    // staticVar는 프로그램 종료 시까지 유지
}

 

 


 

 

3. 특수한 경우들

3-1. null 참조로 static 접근

public class NullStaticAccess {
    static int value = 100;
    int instanceValue = 200;
    static void staticMethod() {
        System.out.println("Static method called");
    }
    
    public static void main(String[] args) {
        NullStaticAccess obj = null;
        
        // 신기한 현상: 에러가 발생하지 않음!
        System.out.println(obj.value);          // 100 출력
        obj.staticMethod();                     // "Static method called" 출력
        
        // 인스턴스 멤버는 NullPointerException 발생
        // System.out.println(obj.instanceValue);  // 에러!
    }
}

 

왜 에러가 없을까?

  • 컴파일러가 obj.value를 NullStaticAccess.value로 자동 변환
  • static은 클래스에 속하므로 객체 상태와 무관
  • 바이트코드 레벨에서는 클래스 참조로 처리됨

주의사항: 이런 코드는 혼란을 야기하므로 실제로는 사용하지 말 것!


3-2. static import 활용

// Math 클래스의 static 메서드들을 직접 사용
import static java.lang.Math.*;

public class StaticImportExample {
    public static void main(String[] args) {
        // Math.sqrt() 대신 sqrt() 직접 사용 가능
        double result = sqrt(pow(3, 2) + pow(4, 2));
        System.out.println("빗변의 길이: " + result);
        
        // Math.PI 대신 PI 직접 사용
        double area = PI * pow(5, 2);
        System.out.println("원의 넓이: " + area);
    }
}

 

static import 사용 기준:

  • 자주 사용하는 유틸리티 메서드
  • 코드가 더 읽기 쉬워지는 경우
  • 남용하면 오히려 가독성 저하

3-3. main 메서드와 static

public class MainMethodExample {
    int instanceVar = 100;
    
    public static void main(String[] args) {
        // main도 static이므로 같은 제약이 적용됨
        
        // 불가능: 인스턴스 변수 직접 접근
        // System.out.println(instanceVar);  // 컴파일 에러!
        
        // 가능: 객체 생성 후 접근
        MainMethodExample obj = new MainMethodExample();
        System.out.println(obj.instanceVar);  // 100 출력
        
        // 가능: static 메서드 호출
        helper();
    }
    
    public static void helper() {
        System.out.println("Helper method called");
    }
}

 

 


 

4. static 활용 실습

4-1. 객체 개수 세기

public class ObjectCounter {
    private static int count = 0;
    private String name;
    
    public ObjectCounter(String name) {
        this.name = name;
        count++;  // 객체 생성 시마다 카운트 증가
        System.out.println(name + " 생성됨. 총 객체 수: " + count);
    }
    
    public static int getCount() {
        return count;
    }
    
    // 소멸자는 없지만 GC 이전에 호출되는 메서드
    @Override
    protected void finalize() throws Throwable {
        count--;  // 실제로는 GC 타이밍 문제로 권장하지 않음
        super.finalize();
    }
}

// 사용 예시
ObjectCounter obj1 = new ObjectCounter("객체1");  // 총 객체 수: 1
ObjectCounter obj2 = new ObjectCounter("객체2");  // 총 객체 수: 2
System.out.println("현재 객체 수: " + ObjectCounter.getCount());  // 2

4-2. 공유 설정 관리

public class AppConfig {
    private static String environment = "development";
    private static boolean debugMode = true;
    private static int maxConnections = 100;
    
    // 외부에서 변경하지 못하도록 private 생성자
    private AppConfig() {}
    
    public static String getEnvironment() {
        return environment;
    }
    
    public static void setEnvironment(String env) {
        environment = env;
        System.out.println("환경 설정 변경: " + env);
    }
    
    public static boolean isDebugMode() {
        return debugMode;
    }
    
    public static void setDebugMode(boolean debug) {
        debugMode = debug;
        System.out.println("디버그 모드: " + (debug ? "ON" : "OFF"));
    }
}

// 사용 예시 - 어디서든 동일한 설정에 접근
AppConfig.setEnvironment("production");
if (AppConfig.isDebugMode()) {
    System.out.println("디버그 정보 출력");
}

 

 


 

5. static 이해 ~퀴즈~

5-1. static 제약사항

다음 코드에서 컴파일 에러가 발생하는 라인은?

public class StaticQuiz {
    int instanceVar = 10;
    static int staticVar = 20;
    
    public static void testMethod() {
        System.out.println(staticVar);        // 라인 1
        System.out.println(instanceVar);      // 라인 2  
        StaticQuiz obj = new StaticQuiz();
        System.out.println(obj.instanceVar);  // 라인 3
        staticHelper();                       // 라인 4
        obj.instanceHelper();                 // 라인 5
    }
    
    public static void staticHelper() {
        System.out.println("Static helper");
    }
    
    public void instanceHelper() {
        System.out.println("Instance helper");
    }
}

 

정답: 라인 2에서 컴파일 에러 발생
이유: static 메서드에서 인스턴스 변수에 직접 접근할 수 없음


5-2. 실행 결과 예측

public class ExecutionQuiz {
    static int x = 10;
    
    static {
        x = 20;
        System.out.println("Static block: x = " + x);
    }
    
    public ExecutionQuiz() {
        x = 30;
        System.out.println("Constructor: x = " + x);
    }
    
    public static void main(String[] args) {
        System.out.println("Main start: x = " + x);
        new ExecutionQuiz();
        new ExecutionQuiz();
        System.out.println("Main end: x = " + x);
    }
}

 

정답:

Static block: x = 20
Main start: x = 20
Constructor: x = 30
Constructor: x = 30
Main end: x = 30

 

해설: static 블록은 클래스 로딩 시 한 번만 실행, 생성자는 객체 생성 시마다 실행

 

 

 

 

 

마무리

static 키워드의 제약사항과 특수 상황들을 정리하면

  • static 메서드는 static 멤버만 직접 접근 가능
  • 실행 순서: static 블록 → main → 인스턴스 블록 → 생성자
  • null 참조로도 static 멤버 접근 가능 (하지만 사용 금지)
  • static import로 코드 간소화 가능

'공부일기.. > Java' 카테고리의 다른 글

[java] 자바 파일 처리 기본기 가이드 - 주니어 개발자 필수 FILE/FILES  (0) 2025.09.22
[java] static 제대로 활용하기 - 패턴과 함정  (0) 2025.09.21
[java] 자바 메모리 구조 - 김영한~자바  (0) 2025.09.19
[Java] JDK 그놈의 환경변수 설정하는 이유 정리~  (0) 2025.09.07
[JUnit5] @EnabledIfEnvironmentVariable 이해하기  (0) 2025.09.05
'공부일기../Java' 카테고리의 다른 글
  • [java] 자바 파일 처리 기본기 가이드 - 주니어 개발자 필수 FILE/FILES
  • [java] static 제대로 활용하기 - 패턴과 함정
  • [java] 자바 메모리 구조 - 김영한~자바
  • [Java] JDK 그놈의 환경변수 설정하는 이유 정리~
s0-0mzzang
s0-0mzzang
공부한것을 기록합니다...
  • s0-0mzzang
    승민이의..개발일기..🐰
    s0-0mzzang
  • 전체
    오늘
    어제
    • 전체~ (108)
      • 마음가짐..! (10)
      • 공부일기.. (76)
        • weekly-log (6)
        • Spring (19)
        • Java (18)
        • DataBase (10)
        • git (2)
        • JPA (6)
        • kafka (1)
        • Backend Architecture (3)
        • Troubleshooting (삽질..ㅋ) (2)
        • Cloud (1)
        • Docker (2)
        • 알고리즘 (1)
        • 리액트 (2)
        • Infra (3)
      • 하루일기.. (22)
        • 그림일기 (8)
        • 생각일기 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    React
    항해99
    스프링부트
    MySQL
    Paging
    ERD
    StringTokenizer
    TDD
    다짐
    SpringBoot
    리팩토링
    ADC 환경
    spring boot
    인프라 기초
    swagger
    항해플러스
    BufferedReader
    자바
    JPA
    spring
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
s0-0mzzang
[java] static 키워드 - 제약사항~
상단으로

티스토리툴바