4. 인스턴스화를 막으려거든 private 생성자를 사용하라

유틸리티 클래스를 구현할 때 이건 꼭 지켜라!

📌 1. 발표 전 알아야 할 개념

✅ 유틸리티 클래스(Utility Class)

  • 순수 정적 메서드만을 제공하는 클래스

  • 자체적인 상태를 가지지 않고 순수한 기능만 제공한다

  • java.util.Arrays, Math, Collections

📕 2. 왜 정적 메서드만 있는 클래스를 만들까?

2-1. 기본 타입 값이나 배열을 다룰 때

  • Arrays : 배열 조작을 돕는 유틸리티 클래스

import java.util.Arrays;
// 배열 관련 메서드
public class UtilArray {
    public static void main(String[] args) {
        int[] arr = {1, 5, 2};
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
        // 출력 : [1, 2, 5]
    }
}

2-2. 특정 인터페이스를 구현하는 객체를 생성하는 메서드

  • Collections : 불변 리스트를 만들어줌

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class CollectionsExample {
    public static void main(String[] args) {
         // 고정된 크기의 리스트를 생성
        List<String> list = Arrays.asList("A", "B", "C");

        // Collections.unmodifiableList(list) → 읽기 전용 리스트로 변환
        List<String> unmodifiableList = Collections.unmodifiableList(list);

        // 리스트에 요소 추가 시도 → 불변 리스트이므로 예외 발생
        // UnsupportedOperationException)
        unmodifiableList.add("D");
    }
}

2-3. final 클래스와 관련된 메서드

  • String, Ingeter, LocalDate

  • final 클래스들은 상속이 불가하기 때문에 관련 추가 메서드는 유틸리티 클래스로 제공한다.

// String 관련 추가 메소드
// Objects 유틸리티 클래스
import java.util.Objects;

public class StringUtilityExample {
    public static void main(String[] args) {
        String str = null;
        System.out.println(Objects.isNull(str));
        // 출력 : true
    }
}

🔑 3. 인스턴스화를 막아야 한다

  • 유틸리티 클래스의 경우 모든 메서드가 static이므로 객체 생성 없이 호출이 가능하다(유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한 클래스가 아니다!)

  • 불필요한 객체 생성은 메모리 낭비

[확장] 인스턴스화를 막을 수 있지 않나요?

  • 생성자를 명시하지 않으면? (❌)

    • 생성자를 명시하지 않으면, 자동으로 매개변수를 받지 않는 public 생성자 생성

  • 추상 클래스로 만들면? (❌)

    • abstract를 사용하는 추상 클래스는, new를 사용한 인스턴스 생성이 불가능하다

    • 단, 상속을 통해 하위 클래스를 만들면 인스턴스를 생성할 수 있다!

      // 추상 메서드
      abstract class Animal {
         abstract void sound();
      }
      
      class Dog extends Animal {
         @Override
         void sound() {
             System.out.println("멍");
         }
      }
      
      // Main 클래스
      public class Main {
         public static void main(String[] args) {
             // Animal myDog = new Animal(); // 추상 클래스 생성 불가
             // 상속 받은 하위 클래스 인스턴스 생성 가능능
             Animal myDog = new Dog();
             myDog.sound();
         }
      }

💥 4. private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다

  • 명시적으로 생성자가 private 이므로 클래스 바깥에서 접근 불가

  • 어떠한 환경에서도 클래스가 인스턴스화하는 것을 막아준다

public class UtilClass {
    private UtilClass() {
    }

    public static void utilMethod() {
        System.out.println("인스턴스화 방지");
    }
}

[추가 개념] enum이 기본적으로 private 생성자를 가지는 것과 동일한 원리

--

💨 향후 확장 포인트

  • private 생성자를 사용하는 것은 상속을 금지하는 것과도 같다

  • [Item19] 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라.


🤖 최종 결론

유틸리티 클래스는 인스턴스화할 필요가 없으므로 private 생성자를 사용해 인스턴스화를 막자자


❗어려웠던 점

  • final 클래스의 개념이 헷갈려 예시 작성에 어려움을 겪었다. String과 같은 쉬운 예제로 치환하니 이해가 쉬웠다.


😶‍🌫️ 느낀점

  • 자바의 클래스 설계 방식은 다양하다

  • 처음 아이템을 읽었을 때 enum의 private 생성자와 비슷한 거 아닌가? 하는 생각이 들었는데 맞았다! 천천히 분석해서 읽고 내가 아는 기존의 개념과 연관짓는 습관을 들이자.

Last updated