1. 실무 활용 패턴
1-1. 유틸리티 클래스 패턴
가장 일반적인 static 활용법
public class MathUtils {
// 인스턴스 생성 방지
private MathUtils() {
throw new AssertionError("유틸리티 클래스는 인스턴스화할 수 없습니다.");
}
public static int max(int a, int b) {
return a > b ? a : b;
}
public static double average(int[] numbers) {
if (numbers.length == 0) return 0;
return (double) sum(numbers) / numbers.length;
}
public static int sum(int[] numbers) {
int total = 0;
for (int num : numbers) {
total += num;
}
return total;
}
public static int factorial(int n) {
if (n < 0) throw new IllegalArgumentException("음수는 팩토리얼을 계산할 수 없습니다.");
if (n <= 1) return 1;
return n * factorial(n - 1);
}
}
// 사용 예시
int[] scores = {85, 90, 78, 92, 88};
double avg = MathUtils.average(scores);
int maxScore = MathUtils.max(90, 85);
int fact5 = MathUtils.factorial(5); // 120
유틸리티 클래스의 특징:
- 상태를 갖지 않는 순수 함수들로 구성
- private 생성자로 인스턴스화 방지
- 모든 메서드가 static
- 재사용 가능한 범용 기능 제공
1-2. 싱글톤 패턴에서 static 활용
public class DatabaseConnection {
private static DatabaseConnection instance;
private String connectionString;
private DatabaseConnection() {
connectionString = "jdbc:mysql://localhost:3306/mydb";
}
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
// 스레드 안전한 버전
public static synchronized DatabaseConnection getInstanceSafe() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public void connect() {
System.out.println("데이터베이스 연결: " + connectionString);
}
}
// 사용 예시
DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();
System.out.println(db1 == db2); // true - 같은 인스턴스
1-3. 팩토리 패턴에서 static 메서드
public class Car {
private String model;
private String color;
private int price;
private Car(String model, String color, int price) {
this.model = model;
this.color = color;
this.price = price;
}
// 정적 팩토리 메서드들
public static Car createSedan(String color) {
return new Car("세단", color, 2500);
}
public static Car createSUV(String color) {
return new Car("SUV", color, 3500);
}
public static Car createSportsCar() {
return new Car("스포츠카", "빨간색", 5000); // 기본 색상
}
public static Car createEconomyCar() {
return new Car("경차", "흰색", 1200);
}
@Override
public String toString() {
return model + "(" + color + ") - " + price + "만원";
}
}
// 사용 예시
Car sedan = Car.createSedan("검은색");
Car suv = Car.createSUV("흰색");
Car sports = Car.createSportsCar();
Car economy = Car.createEconomyCar();
System.out.println(sedan); // 세단(검은색) - 2500만원
System.out.println(sports); // 스포츠카(빨간색) - 5000만원
정적 팩토리 메서드의 장점:
- 생성자보다 이름이 명확함
- 반환 타입의 하위 타입 객체 반환 가능
- 입력 매개변수에 따라 매번 다른 클래스 객체 반환 가능
- 객체 생성을 캡슐화
1-4. 상수 관리 패턴
public class Constants {
// 수학 상수들
public static final double PI = 3.14159265359;
public static final double E = 2.71828182846;
// 애플리케이션 설정
public static final int MAX_RETRY_COUNT = 3;
public static final String DEFAULT_ENCODING = "UTF-8";
public static final long TIMEOUT_MILLIS = 5000L;
// HTTP 상태 코드
public static final int HTTP_OK = 200;
public static final int HTTP_NOT_FOUND = 404;
public static final int HTTP_SERVER_ERROR = 500;
// 데이터베이스 설정
public static final String DB_DRIVER = "com.mysql.cj.jdbc.Driver";
public static final int DB_CONNECTION_POOL_SIZE = 10;
private Constants() {} // 인스턴스화 방지
}
// 사용 예시
double area = Constants.PI * radius * radius;
String encoding = Constants.DEFAULT_ENCODING;
2. 자주하는 실수들
2-1. static 컨텍스트에서 인스턴스 멤버 접근
public class CommonMistake1 {
int instanceVar = 10;
public static void main(String[] args) {
// 잘못된 방법 - 컴파일 에러!
// System.out.println(instanceVar);
// 올바른 방법
CommonMistake1 obj = new CommonMistake1();
System.out.println(obj.instanceVar);
}
public static void staticMethod() {
// 잘못된 방법 - 컴파일 에러!
// instanceMethod();
// 올바른 방법
CommonMistake1 obj = new CommonMistake1();
obj.instanceMethod();
}
public void instanceMethod() {
System.out.println("인스턴스 메서드 호출됨");
}
}
2-2. static 변수의 메모리 누수
public class MemoryLeakExample {
// 위험: static 컬렉션은 GC되지 않음
private static List<String> cache = new ArrayList<>();
private static Map<String, Object> globalMap = new HashMap<>();
public void addData(String data) {
cache.add(data); // 계속 누적되어 메모리 누수 발생 가능
}
public void cacheObject(String key, Object value) {
globalMap.put(key, value); // 메모리 누수 위험
}
// 개선 방법들
// 1. 주기적으로 정리하는 메서드 제공
public static void clearCache() {
cache.clear();
globalMap.clear();
}
// 2. 크기 제한
public void addDataSafely(String data) {
if (cache.size() >= 1000) {
cache.subList(0, 500).clear(); // 절반 정리
}
cache.add(data);
}
// 3. WeakReference 사용 (고급)
private static Map<String, WeakReference<Object>> weakCache = new HashMap<>();
public void cacheObjectSafely(String key, Object value) {
weakCache.put(key, new WeakReference<>(value));
// GC가 일어나면 자동으로 정리됨
}
}
2-3. static import 남용
// 나쁜 예: 너무 많은 static import
import static java.lang.Math.*;
import static java.lang.System.*;
import static java.util.Collections.*;
import static java.util.Arrays.*;
public class StaticImportBad {
public void badExample() {
// 어떤 클래스의 메서드인지 불분명
double result = sqrt(pow(2, 3));
sort(asList(3, 1, 2));
out.println(result);
}
}
// 좋은 예: 필요한 것만 선별적으로 import
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;
public class StaticImportGood {
public void goodExample() {
double result = sqrt(pow(2, 3));
System.out.println(result); // System은 명시적으로
// 또는 자주 사용하는 것만
Arrays.sort(new int[]{3, 1, 2}); // Arrays는 명시적으로
}
}
2-4. 테스트하기 어려운 static 메서드
// 테스트하기 어려운 코드
public class TimeUtils {
public static String getCurrentTimeString() {
return new Date().toString(); // 현재 시간에 의존 - 테스트할 때마다 결과가 다름
}
public static boolean isWeekend() {
Calendar cal = Calendar.getInstance();
int day = cal.get(Calendar.DAY_OF_WEEK);
return day == Calendar.SATURDAY || day == Calendar.SUNDAY;
}
}
// 개선된 코드 - 외부 의존성을 매개변수로 분리
public class ImprovedTimeUtils {
public static String formatTime(Date time) {
return time.toString(); // 매개변수로 받아서 테스트 가능
}
public static boolean isWeekend(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
int day = cal.get(Calendar.DAY_OF_WEEK);
return day == Calendar.SATURDAY || day == Calendar.SUNDAY;
}
// 편의 메서드는 따로 제공
public static boolean isToday Weekend() {
return isWeekend(new Date());
}
}
핵심
static 메서드는 외부 의존성(현재 시간, 파일 시스템, 네트워크 등)을 최소화하고
매개변수로 받을 수 있도록 설계하면 테스트가 쉬워진다.
3. static 사용
3-1. 언제 static을 사용해야 할까?
사용하면 좋은 경우:
- 유틸리티 메서드 (상태 없는 순수 함수)
- 상수 정의
- 팩토리 메서드
- 싱글톤 패턴
- 애플리케이션 전역 설정
// 좋은 static 사용 예시
public class StringUtils {
public static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
public static String capitalize(String str) {
if (isEmpty(str)) return str;
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
}
3-2. 언제 static을 피해야 할까?
피해야 하는 경우:
- 상태를 가지는 로직
- 테스트가 어려워지는 경우
- 객체 간 상호작용이 필요한 경우
- 다형성을 활용해야 하는 경우
// static을 피해야 하는 예시
public class BadStaticExample {
private static int counter = 0; // 상태를 가짐
public static void processOrder() {
counter++; // 전역 상태 변경
// 테스트할 때 이전 테스트의 영향을 받음
}
}
// 개선된 예시
public class GoodExample {
private int counter = 0; // 인스턴스 상태
public void processOrder() {
counter++; // 인스턴스별 독립적 상태
}
public int getCounter() {
return counter;
}
}
4. static 활용
4-1. Enum과 static의 조합
public enum HttpStatus {
OK(200, "OK"),
NOT_FOUND(404, "Not Found"),
INTERNAL_ERROR(500, "Internal Server Error");
private final int code;
private final String message;
HttpStatus(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() { return code; }
public String getMessage() { return message; }
// static 메서드로 코드로 찾기
public static HttpStatus fromCode(int code) {
for (HttpStatus status : values()) {
if (status.code == code) {
return status;
}
}
throw new IllegalArgumentException("Unknown status code: " + code);
}
}
// 사용 예시
HttpStatus status = HttpStatus.fromCode(404);
System.out.println(status.getMessage()); // "Not Found"
5. 퀴즈
5-1. 활용 퀴즈
다음 중 static 사용이 적절한 것은?
// A. 계산기 유틸리티
public class Calculator {
public static int add(int a, int b) {
return a + b;
}
}
// B. 사용자 관리
public class UserManager {
private static List<User> users = new ArrayList<>();
public static void addUser(User user) {
users.add(user);
}
}
// C. 데이터베이스 연결
public class Database {
private static Connection connection;
public static Connection getConnection() {
if (connection == null) {
connection = createConnection();
}
return connection;
}
}
정답과 해설:
- A: 적절함 - 상태 없는 순수 함수
- B: 부적절함 - 테스트 어렵고 메모리 누수 위험
- C: 상황에 따라 다름 - 싱글톤 패턴이지만 멀티스레드 환경에서 문제 가능
마무리
static 키워드의 실무 활용을 정리하면
활용법
- 유틸리티 클래스로 공통 기능 제공
- 상수 관리와 설정값 정의
- 팩토리 메서드로 객체 생성 캡슐화
- 싱글톤 패턴 구현
주의 할 점
- 메모리 누수 위험성 인지
- 테스트 가능성 고려
- static import 남용 금지
- 전역 상태 최소화
판단 기준
- 상태가 없는가?
- 테스트하기 쉬운가?
- 객체 간 상호작용이 필요한가?
- 메모리 누수 위험은 없는가?
static을 올바르게 사용하면 코드의 재사용성과 성능을 향상시킬 수 있지만
잘못 사용하면 테스트하기 어렵고 유지보수가 힘든 코드가 될 수 있다~
항상 트레이드오프를 고려하여 신중하게 사용하시길~~
'공부일기.. > Java' 카테고리의 다른 글
| [코테] JAVA 백준 알고리즘 시작하기 - 입출력 가이드 !!성능최적화!! (0) | 2025.09.29 |
|---|---|
| [java] 자바 파일 처리 기본기 가이드 - 주니어 개발자 필수 FILE/FILES (0) | 2025.09.22 |
| [java] static 키워드 - 제약사항~ (0) | 2025.09.20 |
| [java] 자바 메모리 구조 - 김영한~자바 (0) | 2025.09.19 |
| [Java] JDK 그놈의 환경변수 설정하는 이유 정리~ (0) | 2025.09.07 |