[Java] Utility class란?

2025. 10. 25. 13:00·Develop note

java를 쓰다보면 Math 함수를 한번쯤을 써봤을겁니다

 

근데 이런 생각 안해보셨나요?

왜 Math는 new로 객체를 만들지 않고,
클래스 이름만으로 기능을 사용할 수 있지...?

 

우테코 프리코스 1주차를 지나오면서 코드를 뜯어보다가 Console을 utility class로 선언한 것을 발견해서, 2주차인 지금 이에 대해서 글을 작성해보려고 합니다!


🤔 일반 클래스 vs 유틸리티 클래스

가장 큰 차이는 객체를 만드느냐, 안 만드느냐입니다.

 

일반적인 클래스는 이렇게 씁니다👇

Scanner sc = new Scanner(System.in);
String input = sc.nextLine();

즉, new 키워드로 객체를 만들어야만 그 안의 기능(nextLine)을 사용할 수 있습니다

이런 클래스들은 인스턴스마다 독립적인 상태(state)를 가지게 됩니다.

 

반면 유틸리티 클래스는 이렇게 사용합니다 👇

String input = Console.readLine();

객체를 만들지 않았는데도 메서드를 바로 사용할 수 있죠?

이게 바로 유틸리티 클래스의 핵심입니다!


🌱 Console 코드 분석

우테코 1주차에서 사용한 Console 코드 내부를 확인하면 이렇게 구성되어 있습니다

package camp.nextstep.edu.missionutils;

import java.util.Scanner;

public class Console {
    private static Scanner scanner;

    private Console() {
    }

    public static String readLine() {
        return getInstance().nextLine();
    }

    public static void close() {
        if (scanner != null) {
            scanner.close();
            scanner = null;
        }
    }

    private static Scanner getInstance() {
        if (scanner == null) {
            scanner = new Scanner(System.in);
        }
        return scanner;
    }
}

 

이 코드를 하나씩 뜯어보면서 설명드리겠습니다

 

1) 생성자

private Console() {
}

생성자 선언은 여러분도 많이 접해봤을꺼라 생각합니다

다만 중요한 점은 private으로 선언에서 외부에서 new Console()을 사용하지 못하게 막아놨다는 점입니다.

 

유틸리티 클래스는 인스턴스를 만들 이유가 거의 없으니 일부러 못 만들게 합니다

 

이렇게 하면 클래스가 오직 정적(static) 매서드만으로 사용된다는 의도가 명확해집니다

 

2) 정적필드

private static Scanner scanner;

static이 붙은 필드는 클래스 단위로 하나만 존재합니다.

즉, Console 클래스 전체에서 공유되는 Scanner 객체가 하나만 유지되는 겁니다.

처음엔 null로 시작하고, 필요할 때 생성하는겁니다

 

3) readline()

public static String readLine() {
    return getInstance().nextLine();
}

외부에서 입력을 받을 때 Console.readLine()처럼 클래스 이름으로 바로 호출할 수 있게 해주는 메서드입니다.

내부적으로 getInstance()를 통해 Scanner를 얻고 nextLine()을 호출해서 입력 라인을 반환합니다.

 

4) getInstance(지연초기화)

private static Scanner getInstance() {
    if (scanner == null) {
        scanner = new Scanner(System.in);
    }
    return scanner;
}

여기가 핵심입니다.

코드를 읽어보면, scanner가 아직 없을 때(즉 첫 호출 시)만 new Scanner(System.in)으로 생성하고 그걸 재사용하고 있습니다.

이 방식 덕분에 프로그램 시작 시점에 불필요하게 자원을 미리 쓰지 않고, 실제로 필요할 때만 생성할 수 있습니다.

 

이를 Lazy Initialization(지연 초기화)라고 부릅니다

 

5) 정리 메서드

public static void close() {
    if (scanner != null) {
        scanner.close();
        scanner = null;
    }
}

이는 입력이 끝났을 때 자원을 정리해주는 메서드입니다.


✨ 그렇다면 객체는 언제 생성되고 언제까지 유지될까?

  • 생성 시점: Console.readLine()(또는 내부적으로 getInstance()가 호출될 때) 처음으로 scanner == null이면 new Scanner(System.in)가 실행되어 생성됩니다.
  • 유지 기간: 생성된 Scanner는 Console 클래스의 정적 필드로 계속 유지됩니다. (프로그램이 끝나거나 Console.close()가 호출될 때까지)
  • 소멸 / 정리: Console.close()를 호출하면 scanner.close()가 실행되고 scanner는 null로 바뀝니다.

정리하자면 아래와 같습니다.

생성 시점 Console.readLine() 첫 호출 시
재사용 그 후 모든 Console.readLine()은 동일한 Scanner 사용
자원 해제 Console.close() 호출 시 (또는 프로그램 종료 시 JVM 종결)

 


🐢 utility class vs 일반 클래스

🚫 유틸리티 클래스를 사용하지 않은 버전

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // 매번 Scanner 객체를 직접 생성해야 함
        Scanner scanner = new Scanner(System.in);

        System.out.print("이름을 입력하세요: ");
        String name = scanner.nextLine();

        System.out.println("안녕하세요, " + name + "님!");

        // 사용이 끝나면 객체 자체를 반드시 닫아줘야 함
        scanner.close();
    }
}

 

✅ 유틸리티 클래스를 사용한 버전

public class Main {
    public static void main(String[] args) {
    	// Scanner 객체를 생성할 필요 없음!
        System.out.print("이름을 입력하세요: ");
        String name = Console.readLine();

        System.out.println("안녕하세요, " + name + "님!");

        Console.close(); // 사용 끝나면 클래스 자체에서 정리
    }
}

💪 마무으리

유틸리티 클래스는 “객체를 매번 만들 필요 없는 도구상자”입니다.

Console은 내부에서 필요한 객체(Scanner)를 필요할 때 한 번 만들어 재사용하고, 끝나면 정리해 주는 전형적인 패턴이라 이해하면 좋습니다.

다만, 전역처럼 공유되는 static 자원이기 때문에 동시성 문제(멀티스레드) 가 발생할 수 있습니다.

싱글스레드에서의 간단한 콘솔 입력용이라면 상관없지만, 멀티스레드 환경에서는 동기화를 고려해줄 필요가 있습니다!

또한 입력 스트림이 닫히고 다른 곳에서 다시 사용하려면 Console.getInstance()로 새로 생성되긴 하지만, System.in 자체를 닫으면 다시 열 수 없는 경우가 종종 발생하기도 하니 close() 호출 시점에 주의해야 합니다!

'Develop note' 카테고리의 다른 글

[트러블슈팅] 공백 입력 테스트코드, NoSuchElementException  (1) 2025.10.28
[Pattern] 예외 팩토리(Exception Factory) 패턴: 깔끔하고 유연한 예외 처리  (1) 2025.10.23
[Git] Git alias 설정은 어떻게 돌아가는가?  (0) 2025.10.20
[Git] Hooks 적용으로 커밋 메세지 형식 강제하기  (0) 2025.10.15
'Develop note' 카테고리의 다른 글
  • [트러블슈팅] 공백 입력 테스트코드, NoSuchElementException
  • [Pattern] 예외 팩토리(Exception Factory) 패턴: 깔끔하고 유연한 예외 처리
  • [Git] Git alias 설정은 어떻게 돌아가는가?
  • [Git] Hooks 적용으로 커밋 메세지 형식 강제하기
youngi2
youngi2
영기의 개발기록입니다!
  • youngi2
    영기의 개발일지
    youngi2
  • 전체
    오늘
    어제
    • 분류 전체보기 (15)
      • Algorithm (10)
      • Develop note (5)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    백준 2618 코틀린
    백준
    백준 7578 kotlin
    백준 7578 코틀린
    7578 코틀린
    예외 팩토리 패턴
    2618 코틀린
    예외팩토리패턴
    2618 kotlin
    commit-msg
    유틸리티클래스
    GIT
    예외팩토리
    백준 7578
    utility class
    7578 kotlin
    Exceptino Factory
    Exception Factory Pattern
    유틸리티 클래스
    백준 2618 kotlin
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
youngi2
[Java] Utility class란?
상단으로

티스토리툴바