Java Nested Classes

원문: Nested Classes (The Java Tutorials > Learning the Java Language > Classes and Objects)

1. 개요

  • 다른 class 안에 정의된 class를 nested class 라고 한다.
class OuterClass {
    ...
    class NestedClass {
        ...
    }
}
  • Nested class 는 다음과 같이 두 종류로 나눌 수 있다.
    Non-static nested class (inner class 라고 한다.)
    static nested class
  • Nested class 는 enclosing class의 멤버이다.
  • Non-static nested class (inner class) 는 enclosing class의 다른 멤버들(private 으로 선언되었다 하더라도)에 대한 접근 권한을 가진다.
  • OuterClass의 멤버로써 nested class 는 private, public, protected, package private 으로 선언될 수 있다. (OuterClass는 public 이나 package private 으로만 선언가능하다.)

2. Nested Class 사용 이유

  • 한 곳에서만 사용되는 class들을 논리적으로 그룹화하는 방법이다.
    Class 가 단지 하나의 다른 class 에만 사용된다면 그 class 에 포함시켜 관리하는 것이 논리적이다.
    “helper classes”를 중첩(nesting)하면 package를 좀 더 능률적으로 만들어준다.
  • encapsulation 을 증가시킨다.
    Top-level의 A, B class 가 있다고 가정하자.
    B는 A의 private으로 선언된 멤버에 대한 접근 권한이 필요하다.
    Class B를 class A에 감춤으로써 A의 멤버는 private 으로 선언될 수 있고 B는 이들을 접근할 수 있다.
    추가로 B 자체는 외부로부터 숨겨질 수 있다.
  • 좀 더 읽고 유지하기 좋은 코드를 유도할 수 있다.
    Top-level class에 작은 class들을 nesting하는 것은 코드를 사용되는 곳에 더 가깝게 위치하도록 한다.

3. Inner Classes

  • Instance methods와 variables와 같이 inner class는 enclosing class의 instance와 연계되며 이 object의 method와 field에 대한 직접적인 접근권한을 가진다.
  • 또한 inner class는 instance와 연계되므로 자체적으로 static member를 정의할 수 없다.
  • InnerClass의 instance는 OutClass의 instance 안에만 존재할 수 있으며 enclosing instance의 method와 field에 대한 직접 접근 권한을 가진다.
  • Inner class를 instance화 하기 위해서는 우선 outer class를 instance화해야 한다.
OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
  • Inner class에는 local class와 anonymous class가 있다.

4. Static Nested Classes

  • Class method와 variable와 같이 static nested class는 outer class와 연계되어 있다.
  • Static class method처럼 static nested class는 enclosing class에 정의되어있는 instance variables나 methods를 직접 참조할 수 없다.
    오직 object reference를 통해서만 가능하다.
  • Static nested class는 결과적으로 다른 top-level class에 있는 top-level class처럼 동작한다.
  • Static nested class는 top-level class와 같은 방식으로 초기화된다.
StaticNestedClass staticNestedObject = new StaticNestedClass();

5. Inner Class와 Nested Static Class

  • 다음 예제는 OuterClass와 TopLevelClass가 나오며 inner class(InnerClass), nested static class(StaticNestedClass), top-level class(TopLevelClass)가 접근할 수 있는 OutClass의 멤버를 보여준다.
//OuterClass.java
public class OuterClass {
    String outerField = "Outer field";
    static String staticOuterField = "Static outer field";

    class InnerClass {
        void accessMember() {
            System.out.println(outerField);
            System.out.println(staticOuterField);
        }
    }

    static class StaticNestedClass {
        void accessMember(OuterClass outer) {
            // Compiler error: Cannot make a static reference to the non-static
            //     field outerField
            // System.out.println(outerField);
            System.out.println(outer.outerField);
            System.out.println(staticOuterField);
        }
    }

    public static void main(String[] args) {
        System.out.println("Inner class:");
        System.out.println("------------");
        OuterClass outerObject = new OuterClass();
        OuterClass.InnerClass innerObject = outerObject.new InnerClass();
        innerObject.accessMembers();

        System.out.println("\nStatic nested class:");
        System.out.println("--------------------");
        StaticNestedClass staticNestedObject = new StaticNestedClass();        
        staticNestedObject.accessMembers(outerObject);
        
        System.out.println("\nTop-level class:");
        System.out.println("--------------------");
        TopLevelClass topLevelObject = new TopLevelClass();        
        topLevelObject.accessMembers(outerObject); 
    }

//TopLevelClass.java
public class TopLevelClass {

    void accessMembers(OuterClass outer) {     
        // Compiler error: Cannot make a static reference to the non-static
        //     field OuterClass.outerField
        // System.out.println(OuterClass.outerField);
        System.out.println(outer.outerField);
        System.out.println(OuterClass.staticOuterField);
    }  
}

//Output
Inner class:
------------
Outer field
Static outer field

Static nested class:
--------------------
Outer field
Static outer field

Top-level class:
--------------------
Outer field
Static outer field

  • Static nested class StaticNestedClass는 outerFiled에 직접 접근할 수 없다. 이유는 enclosing class(OuterClass)의 instance variable이기 때문이다.
  • 비슷하게 top-level class TopLevelClass도 outerField에 직접 접근할 수 없다.

6. Shadowing

  • 특정 영역(inner class나 method definition 같은)에서의 타입 선언(member variable이나 parameter naem)이 enclosing scope내의 다른 선언과 같은 이름을 가지는 경우 그 선언은 enclosing scope의 선언을 shadow하게 된다. shadow된 선언을 이름만으로 참조할 수 없다.
  • 다음 예제(ShadowTest)는 이점을 보여준다.
public class ShadowTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
        }
    }

    public static void main(String... args) {
        ShadowTest st = new ShadowTest();
        ShadowTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

//Result
x = 23
this.x = 1
ShadowTest.this.x = 0
  • 이 예제는 세 개의 변수 x를 정의한다. the member variable of the class ShadowTest, the member variable of the inner class ShadowTest the parameter in the method methodInFirstLevel
  • method methodInFirstLevel의 parameter로 정의된 x는 FirstLevel의 변수를 shadow한다.
  • 결과적으로 method methodInFirstLevel안에서 변수x를 사용할 때 method parameter를 참조한다. inner class FirstLevel의 member 변수를 참조하기 위해서는 enclosing scope를 나타내는 this 키워드를 사용한다.
  • 클래스 명을 사용하여 더 큰 scope에 있는 member variable을 참조한다.
    예를 들어, 다음 statement는 class의 member variable을 접근한다. System.out.println(“ShadowTest.this.x = “ + ShadowTest.this.x);

7. Serialization

  • local, anonymous class를 포함한 inner class의 직렬화는 권장되지 않는다.
  • Java compiler가 inner class와 같은 특정 construct(class, method, field, 소스상의 construct와 연관되지 않는 다른 construct)를 컴파일 할 때 synthetic construct를 만든다. Synthetic construct는 java compiler가 JVM의 변환없이 새로운 java language를 실행하도록 한다.
  • 서로 다른 java compiler implementation들 간에 synthetic construct는 매우 다양할 수 있다. ( .class 파일이 implementation간에 다양할 수 있다는 말이다.)
  • 결론적으로 서로 다른 JRE implementation에서 inner class를 serialize하고 deserialize한다면 compatibility issue가 발생할 것이다.

댓글남기기