본문 바로가기

STUDY/이펙티브자바

4-10) 멤버 클래스는 되도록 static으로 만들어라

중첩 클래스

 

중첩 클래스란 다른 클래스 안에 정의된 클래스로서 이너 클래스(inner class)라고도 불린다. 중첩 클래스는 자신을 감싼 외부 클래스에서만 쓰여야 하며, 그 이외에 쓰임새가 있다면 탑레벨 클래스로 만들어야 한다. 중첩클래스는 1) 정적 멤버 클래스 2) 멤버 클래스 3) 익명 클래스 4) 지역 클래스와 같이 4개의 유형이 있다.

 

 


정적 멤버 클래스

 

public class MyClass {
    private Integer myNumber;
    
    public static class MyInnerClass {
        public void method(MyClass myClass) {
            Integer number = myClass.myNumber;
        }
    }
}

// 일반 클래스와 같이 사용할 수 있다.
MyInnerClass instance = new MyClass.MyInnerClass();

클래스의 정적 멤버 필드처럼 정적(static)으로 정의된 멤버 클래스를 정적 멤버 클래스라고 한다. 위 예제를 보면 MyClass 내부에 MyInnerClass가 정의되어 있다. 이 클래스는 일반적인 클래스와 같이 new를 통해 만들수 있고 필드를 가지며 메소드를 정의할 수 있다. 하지만 자신을 감싼 외부 클래스의 private 필드에 접근 가능하다는 것이 일반 클래스와 다르다.

 

public class Calculator {
	public static enum Operator {
		PLUS, MINUS, DIVIDE, MULTIPLY;
		...
	}
	...
}

Calculator 클래스 내부의 Operator 열겨형은 Calculator.Operator.PLUS와 같이 계산 유형을 선택하는 역할을 한다. 여기서 Operator는 Calculator 클래스 내부에서만 사용되기 때문에 새로운 탑레벨 클래스를 만들지 않고 내부의 중첩 클래스로 정의하였다.

 

 


(비정적) 멤버 클래스

 

public class VendingMachine {
    private String status;
    
    public class Button {
        public void click() {
            String status = VendingMachine.this.status;
        }
    }
}

// 비정적 멤버 클래스이기 때문에 감싸는 외부 클래스의 인스턴스가 있어야 한다.
VendingMachine vm = new VendingMachine();
Button button = vm.new Button();

(비정적) 멤버 클래스는 정적 멤버 클래스와 달리 static 키워드를 쓰지 않고 정의한다. 정의할때 키워드 하나 차이지만 사용에 있어서는 큰 차이가 있다. 먼저 비정적 멤버 클래스이기 때문에 객체를 만들기 위해서는 먼저 감싸는 외부 클래스의 인스턴스가 있어야 한다. 그리고 이 말은 즉 생성되는 멤버 클래스의 객체는 외부 클래스의 인스턴스와 연결되어 있다는 뜻이다. 위 예제에서 VendingMachine.this.status과 같이 외부 클래스의 인스턴스에 접근해서 멤버 변수를 가져올 수 있다.

 

(비정적) 멤버 클래스는 외부 클래스의 인스턴스와 연결되다는 특성 때문에 사용이 지양된다. 멤버 클래스는 먼저 외부 클래스의 인스턴스가 있어야 클래스를 사용할 수 있다. 그리고 이는 일반적인 클래스의 사용과 다르기 때문에 사용이 불편하다. 또 멤버 클래스와 외부 클래스의 인스턴스의 연결 정보를 가지고 있어야하기 때문에 사용되는 메모리가 많다. 따라서 상위 클래스의 인스턴스에 연결이 필요한 특수한 경우가 아니라면 멤버클래스에는 static 키워드를 사용하여 정적으로 만드는 것이 권장된다.

 

 


익명 클래스

 

public class MainClass {
	public static void main(String[] args) {
        Object instance1 = new Object();
		Object instance2 = new Object(){
		};
		Object instance3 = new Object(){
		};
		
		System.out.println(instance1.getClass().getTypeName());
		System.out.println(instance2.getClass().getTypeName());
		System.out.println(instance3.getClass().getTypeName());
	}
}

// 출력결과
java.lang.Object
effectivejava.MainClass$1
effectivejava.MainClass$2

일반적으로 클래스는 이름을 가지고 정의되고 지속적으로 사용된다. 하지만 익명 클래스는 이름이 없고 단 한번만 사용되는 클래스이다. 위의 예제를 보면 instance1, instance2, instance3이 있다. instance1은 평볌하게 new 키워드를 사용하여 만든 Object 클래스의 객체이다. instance2와 instance3도 같아보이지만 다른점이 있다. Object(); 와 같이 괄호를 닫고 끝나는게 아니라 Object(){};와 같이 중괄호가 하나 더 나온다. 이는 Object Class의 객체를 만드는게 아니다. Object 클래스를 상속한 새로운 익명 클래스를 만들고 객체를 생성한 것이다. 실제로  instance1, instance2, instance3의 타입명 출력 결과를 보면 instance1은 Object라고 나오지만 instance2, instance3은 익명 클래스이기 때문에 임시로 정의된 이름이 나온다.

 

 


지역 클래스

 

public class MainClass {
    public static void method() {
        // 지역 클래스 선언 
        class MyLocalClass {
        }
		
        MyLocalClass instance = new MyLocalClass();
    }
	
    public static void main(String[] args) {
        // 컴파일 에러 발생
        MyLocalClass instance = new MyLocalClass();
    }
}

지역 클래스는 일반 클래스와 유사하게 사용할 수 있지만, 클래스의 타입을 사용할 수 있는 범위가 클래스가 정의된 지역을 벗어 날 수 없다. 메소드에서 선언한 지역변수는 메소드를 벗어나면 사용할 수 없는 것과 같다. 위의 예제코드를 보면 method() 에서 MyLocalClass가 정의되었고 new 키워드를 통해 인스턴스를 만들었다. 이처럼 일반 클래스 유사하게 정의하고 사용할 수 있다. 하지만 main 메소드에서 MyLocalClass의 타입을 사용하려고 하면 컴파일 에러가 발생한다. MyLocalClass는 method()에서 정의된 지역 클래스이기 때문에 method()를 벗어나서는 사용할 수 없는 것이다.

728x90