Java/Effective Java

[이펙티브 자바] 아이템 24.멤버 클래스는 되도록 static으로 만들어라.

메성 2020. 9. 7. 21:47
반응형

 

들어가기에 앞서, 중첩 클래스란 다른 클래스안에 정의된 클래스를 말한다.

중첩 클래스의 종류로는, 정적 멤버 클래스, 멤버 클래스, 익명 클래스, 지역 클래스 로 총 네 가지가 있다.

 

 

이번 아이템에서는 각각의 중첩 클래스의 특징을 살펴보자.

 

 

먼저 중첩 클래스를 왜 사용하는가를 보자.

  • 내부 클래스에서 외부 클래스의 멤버에 손쉽게 접근할 수 있다.
  • 서로 관련 있는 클래스들을 논리적으로 묶어, 코드의 캡슐화를 증가시킬 수 있다.
  • 외부에서 내부 클래스에 접근할 수 없으므로 코드의 복잡성을 줄일 수 있다. 또한, 외부 클래스의 복잡한 코드를 내부 클래스로 옮겨 코드 복잡성을 줄일 수 있다.

 

정적 멤버 클래스

해당 클래스는 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에도 접근할 수 있다는 점을 제외하고는 일반 클래스와 동일하다.

외부 클래스를 간편하게 사용하기 위한 목적으로 쓰이고 대표적인 예로 builder 패턴이 있다.

public class NutritionFacts {
  private final int servingSize;
  private final int servings;
  private final int calories;
  private final int fat;

  public static class Builder {
    // 필수 매개변수
    private final int servingSize;
    private final int servings;

    // 선택 매개변수 - 기본값으로 초기화한다.
    private int calories      = 0;
    private int fat           = 0;

    public Builder(int servingSize, int servings) {
      this.servingSize = servingSize;
      this.servings    = servings;
    }

    public Builder calories(int val) {
      calories = val;      
      return this; 
    }
    public Builder fat(int val) {
      fat = val;           
      return this; 
    }

    public NutritionFacts build() {
      return new NutritionFacts(this);
    }
  }

  public NutritionFacts(Builder builder) {
    servingSize  = builder.servingSize;
    servings     = builder.servings;
    calories     = builder.calories;
    fat          = builder.fat;
  }

  public static void main(String[] args) {
    NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
      .calories(100).build();
  }
}

정적 멤버 클래스는 외부 클래스를 사용할 때 자연스럽게 내부 클래스를 호출하여 외부 클래스의 생성을 도와주는 헬퍼 클래스이다.

즉, 외부 클래스를 인스턴스화할 때 매번 내부 클래스도 재생성하는 것은 메모리 측면에서 비효율적이기 때문에 static으로 선언하여 재사용하는 것이다. 단, 내부 클래스에서는 외부 클래스 객체를 참조하지 않을 때에만 정적으로 선언이 가능하다.

 

 

비정적 멤버 클래스

비정적 멤버 클래스의 경우 바깥 클래스의 인스턴스와 암묵적으로 연결된다. 이로 인해, 비정적 멤버 클래스의 인스턴스 메소드에서 this를 통해 외부 클래스의 메소드를 호출하거나 외부 클래스를 참조할 수 있다.

public class ClassA {
  public int a = 0;

  public void print() {
    System.out.println("pring ClassA");
    ClassB b = new ClassB();
    b.print();
  }

  public class ClassB {
    public void print() {
      //ClassA.this를 통해 ClassA에 접근이 가능.
      System.out.println("print ClassB : " + ClassA.this.a);
    }
  }
}


public class ClassAMain {
    public static void main(String[] args) {
        ClassA a = new ClassA();
        a.print();
                System.out.println("================");
        ClassA.ClassB b = a.new ClassB();
        b.print();
    }
}
//결과 
//pring ClassA
//print ClassB : 0
//================
//print ClassB : 0

거대한 외부 클래스가 존재할 때, 가독성을 높이기 위해 외부 클래스 내의 중요 기능과 속성을 따로 내부 클래스에서 정의한다.

 

 

익명 클래스

익명 클래스는 이름이 없는 클래스이고 외부 클래스의 멤버 클래스도 아니다. 사용되는 시점에서 선언과 동시에 인스턴스가 만들어진다. 또한, 익명 클래스가 상위 타입(자기 자신 혹은 부모)에 상속한 멤버 외에는 호출할 수가 없다.

public class AnonymousClsParent {
  public void print() {}
}

public class AnonymousCls extends AnonymousClsParent{
  public void save() {
    System.out.println("save");
  }
}

public static void main(String[] args) {
  AnonymousCls a = new AnonymousCls() {
    public String cd = "copy";
    public void copy() {
      System.out.println("copy");
    }

    @Override
    public void save() {
      System.out.println("save from main");
      super.save();
    }


    @Override
    public void print() {
      System.out.println("print from main");
      super.print();
    }
  };

  //a.copy() 호출 불가
  a.save();
}

//결과
//save from main
//save
//print from main

추상화 되어 있는 클래스를 재정의하여 전역 필드로 사용할 때 주로 사용된다.

자바가 람다(1.8)를 지원하기 전에는 즉석에서 작은 함수 객체(단 하나의 추상 메소드)나 처리 객체를 만드는데 익명 클래스를 주로 사용했다. 하지만 람다가 나타나면서 익명 클래스 보다는 람다를 활용하고 있다.(아이템 42. 익명 클래스 보다는 람다를 사용하라)

 

 

지역 클래스

지역 클래스는 네 가지 중첩 클래스 중 가장 드물게 사용되고 있다.

지역 클래스의 경우 지역 변수를 선언할 수 있는 곳이라면 어디든 선언이 가능하고 유효 범위(메소드 종료 시 메모리에 사라짐)도 지역 변수와 동일하다.

또한, 멤버 클래스 이름을 반복해서 사용할 수 있고, 익명 클래스 처럼 비정적 문맥에서만 외부 바깥 인스턴스를 참조할 수 있으며, 정적 멤버는 가질 수 없다.

public class LocalClass {
  private String name = "lim";
  public void print(){
    System.out.println(name);
  }

  public void localCls() {
    //지역 클래스 정의
    class LocalCls{
      public void print2() {
        //외부 클래스 메소드 접근
        print();
        System.out.println("local class1 print");
      }
    }
    LocalCls localCls = new LocalCls();
    localCls.print2();
  }

  public void localCls2() {
    //같은 이름의 지역 클래스 정의
    class LocalCls{
      public void print3() {
        System.out.println("lcoal class 2 print");
      }
    }
    LocalCls localCls = new LocalCls();
    localCls.print3();
  }
}

함수 내에서 간단한 비동기적 처리를 위해 객체를 만들어 사용할 때 쓰인다.

 

 

정리

멤버 클래스가 외부 클래스의 인스턴스를 참조하지 않는다면, 멤버 클래스를 static으로 만드는 것이 좋다. 멤버 클래스를 static으로 만들게 되면 외부 클래스를 반복적으로 생성할 때,  내부 인스턴스를 반복적으로 재생성할 비용이 사라지게 되고, 쓸데없는 heap 영역의 메모리 공간을 낭비하지 않게 된다.

 

이펙티브 자바 Effective Java 3/E
국내도서
저자 : 조슈아 블로크(Joshua Bloch) / 이복연(개앞맵시)역
출판 : 인사이트 2018.11.01
상세보기
반응형