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 |