아이템 25. 톱레벨 클래스는 한 파일에 하나만 담으라. - ksw6169/effective-java GitHub Wiki

파일 하나에 톱레벨 클래스를 여러 개 선언하는 것은 위험하다.

  • 소스 파일 하나에 톱레벨 클래스를 여러 개 선언하더라도 자바 컴파일러는 불평하지 않는다.
  • 하지만 아무런 득이 없을 뿐더러 심각한 위험을 감수해야 한다.
  • 이렇게 하면 한 클래스를 여러가지로 정의할 수 있으며, 그중 어느 것을 사용할지는 어느 소스 파일을 먼저 컴파일하냐에 따라 달라지기 때문이다.

예제 - 두 클래스를 한 파일에 정의

  • 두 클래스를 한 파일에 정의한 Utensil.java와 Dessert.java 소스 파일이 있다고 하자.
// Utensil.java
class Utensil {
    static final String NAME = "pan";
}

class Dessert {
    static final String NAME = "cake";
}
// Dessert.java
class Utensil {
    static final String NAME = "pot";
}

class Dessert {
    static final String NAME = "pie";
}
  • 운 좋게 javac Main.java Dessert.java 명령으로 컴파일한다면 컴파일 오류가 나고 두 소스 파일이 클래스를 중복 정의했다고 알려줄 것이다.
  • 하지만 javac Main.java나 javac Main.java Utensil.java 명령으로 컴파일하면 potpie를 출력한다.
// Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(Utensil.NAME + Dessert.NAME);
    }
}
  • 이처럼 컴파일러에 어느 소스 파일을 먼저 건네느냐에 따라 동작이 달라지므로 반드시 바로 잡아야 할 문제다.

해결책 - 정적 멤버 클래스를 사용한다.

  • 해결책은 톱레벨 클래스를 서로 다른 클래스로 분리하는 것이다.
  • 굳이 여러 톱레벨 클래스를 한 파일에 담고 싶다면 정적 멤버 클래스를 사용할 수 있다.
  • 다른 클래스에 딸린 부차적인 클래스라면 정적 멤버 클래스로 만드는 쪽이 일반적으로 더 나을 것이다. 읽기 좋고, 접근 범위도 최소로 관리할 수 있기 때문이다.
public class Test {
    public static void main(String[] args) {
        System.out.println(Utensil.NAME + Dessert.NAME);
    }

    private static class Utensil {
        static final string NAME = "pan";
    }

    private static class Dessert {
        static final String NAME = "cake";
    }
}

정리

  • 소스 파일 하나에는 반드시 톱레벨 클래스(혹은 톱레벨 인터페이스)를 하나만 담자
  • 이 규칙만 따른다면 컴파일러가 한 클래스에 대한 정의를 여러 개 만들어내는 일은 사라진다.
  • 그렇게 하면 소스 파일을 컴파일하는 순서에 따라 프로그램의 동작이 달라지는 일은 결코 일어나지 않을 것이다.

참고 자료

  • Effective Java 3/E