정적(static) 메서드와 정적(static) 필드만을 담은 클래스는 객체지향과는 거리가 있지만 쓰임새가 있다.
java.lang.Math
와 java.util.Arrays
처럼 기본 타입 값이나 배열 관련 메서드들을 모아놓는다.java.util.Collections
처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩토리)를 모아놓을 수도 있다.final
클래스와 관련한 메서드들을 모아놓을 때도 사용한다.[정적 메서드와 정적 필드만을 담은 클래스]
public class Decision {
private static String answer;
private static String guessAnswer;
public static boolean isCorrect() {
return answer.equals(guessAnswer);
}
}
정적 멤버만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계한것이 아니다. 하지만 생성자를 명시하지 않으면 컴파일러가 자동으로 기본생성자(매개변수를 받지 않는 public
생성자)를 만든다.
Decision decision = new Decision();
boolean isCorrect1 = Decision.isCorrect();
// 잘못된 사용법
boolean isCorrect2 = Decision.isCorrect();
// 올바른 사용법
추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다.
abstract class Decision {
...
}
class SubDecision extends Decision{
...
}
...
public static void main(String[] args) {
// abstract 클래스는 인스턴스화 불가능
// Decision decision = new Decision();
// 하위 클래스는 인스턴스화 가능
SubDecision subDecision = new SubDecision();
}
public class Decision {
private Decision() {
throw new AssertionError();
// 예외를 던진 이유는 클래스 안에서 실수로라도 생성자를 호출하지 않도록 하기 위함이다.
}
...
public static void main(String[] args) {
// 'Decision()' has private access in 'Decision'
Decision decision = new Decision();
}
생성자를 private
접근제어자로 추가할 경우 명시적 생성자가 private
이기 때문에 클래스 바깥에서는 접근할 수 없다.
이 방식은 상속을 불가능하게 한다.
(모든 생성자는 상위 클래스의 생성자를 호출하게 되는데, 이를 private
으로 선언해 접근할 수 없도록 했기 때문이다.)
생성자가 존재하는데 호출할 수 없기때문에 직관적이지 않다.
(따라서 적절한 주석을 달아주는 것이 좋다.)
정적 팩터리와 정적 필드만을 담은 유틸리티 클래스를 사용할 경우 인스턴스화를 하려고 설계한것이 아니니 인스턴스화를 막아야한다.
클래스의 인스턴스화를 막는 방법은 추상클래스를 사용하는 방법과 private
생성자를 사용하는 방법이 있다.
추상클래스를 사용하는 방법은 하위클래스를 만들어 인스턴스화를 하는것이 가능해 클래스를 상속해 사용하라는 뜻으로 오해의 소지가 발생할 수 있다.
따라서 인스턴스화와 상속을 모두 불가능하게 하는 private
생성자를 사용하여 인스턴스화를 막겠다는 의지를 확실하게 보여주자.
[Reference]
이펙티브 자바 3판 아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라
이펙티브-자바-아이템-4.-인스턴스화를-막으려거든-private-생성자를-사용하라