[java] static 제대로 활용하기 - 패턴과 함정

2025. 9. 21. 19:35·공부일기../Java

 

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
'공부일기../Java' 카테고리의 다른 글
  • [코테] JAVA 백준 알고리즘 시작하기 - 입출력 가이드 !!성능최적화!!
  • [java] 자바 파일 처리 기본기 가이드 - 주니어 개발자 필수 FILE/FILES
  • [java] static 키워드 - 제약사항~
  • [java] 자바 메모리 구조 - 김영한~자바
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
    ADC 환경
    Paging
    리팩토링
    swagger
    항해99
    SpringBoot
    StringTokenizer
    spring boot
    TDD
    항해플러스
    BufferedReader
    다짐
    ERD
    MySQL
    자바
    spring
    인프라 기초
    JPA
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
s0-0mzzang
[java] static 제대로 활용하기 - 패턴과 함정
상단으로

티스토리툴바