[Java] 중첩 클래스, 내부 클래스

19. 중첩 클래스, 내부 클래스

중첩 클래스의 분류

  • 정적 중첩 클래스 : static 사용, 바깥 클래스의 인스턴스에 소속 X
  • 내부 클래스 종류 : static 사용 X, 바깥 클래스의 인스턴스에 소속 O
    • 내부 클래스 : 바깥 클래스의 인스턴스의 멤버에 접근한다.
    • 지역 클래스 : 코드 블럭 내에서 클래스를 정의한다.
    • 익명 클래스 : 지역 클래스의 특징 + 클래스의 이름이 없는 특별한 클래스

중첩(Nested) vs. 내부(Inner)

  • 중첩(Nested) : 어떤 다른 것이 내부에 위치하거나 포함되는 구조적 관계. 단순히 위치만 안에 있는 것이다.
    • 바깥 클래스와 관계 없는 전혀 다른 클래스
  • 내부(Inner) : 나의 내부에서 나를 구성하는 요소
    • 바깥 클래스의 내부에 있으면서 바깥 클래스를 구성하는 요소

중첩 클래스 사용 이유

  • 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나, 둘이 아주 긴밀하게 연결되어 있는 특별한 경우에만 사용한다.
    • 외부 여러곳에서 특정 클래스를 사용한다면 중첩 클래스로 사용할 수 없다.
  • 특정 클래스가 다른 하나의 클래스 안에서만 사용될 때 해당 클래스 안에 포함되는 게 논리적으로 더 그룹화된다.
  • 캡슐화 - 중첩 클래스는 바깥 클래스의 private 멤버에 접근 할 수 있다. 중첩 클래스를 사용하면 불필요한 public 메서드를 제거 가능하다.

정적 중첩 클래스

  • 중첩 클래스
    • static 사용
    • 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
    • 바깥 클래스의 클래스 멤버에는 접근할 수 있다.
  • private 접근 제어자
    • private 접근 제어자는 같은 클래스 안에 있을 때만 접근 가능
    • 중첩 클래스는 바깥 클래스의 private 접근 제어자에 접근 할 수 있다.
  • 정적 중첩 클래스는 new 바깥클래스.중첩클래스()로 생성할 수 있다.
public class NestedOuter {
    private static int outClassValue = 3; // 클래스 멤버
    private int outInstanceValue = 2; // 인스턴스 멤버

    static class Nested {
        private int nestedInstanceValue = 1;

        public void print() {
            // 자신의 멤버에 접근
            System.out.println(nestedInstanceValue);

            // 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
            // System.out.println(outInstanceValue);

            // 바깥 클래스의 클래스 멤버에는 접근할 수 있다. private도 접근 가능
            System.out.println(NestedOuter.outClassValue);
        }
    }
}
public class NestedOuterMain {
    public static void main(String[] args) {
        NestedOuter outer = new NestedOuter();
        NestedOuter.Nested nested = new NestedOuter.Nested();
        nested.print();

        System.out.println("nestedClass = " + nested.getClass());
    }
}
  • new NestedOuter()new NestedOuter.Nested()로 만든 정적 중첩 클래스의 인스턴스는 서로 관계가 없는 인스턴스이다.
    • 정적 중첩 클래스는 다른 클래스를 그냥 중첩해둔 것
  • 나의 클래스가 포함된 중첩 클래스가 아닌 다른 곳에 있는 중첩 클래스에 접근 할 때는 바깥클래스.중첩클래스로 접근한다.
    • NestedOuter.Nested nested = new NestedOuter.Nested();
  • 나의 클래스에 포함된 중첩 클래스에 접근할 때는 바깥 클래스 이름을 적지 않는다.
    public class Network {
      public void sendMessage(String text) {
        NetworkMessage networkMessage = new NetworkMessage(text);
      }
    
      private static class NetworkMessage {...}
    }
    


내부 클래스

  • 내부 클래스
    • static이 붙지 않는다.
    • 바깥 클래스의 인스턴스에 소속된다.
    public class InnerOuter {
      private static int outClassValue = 3; //클래스 멤버
      private int outInstanceValue = 2; //인스턴스 멤버
    
      class Inner {
          private int innerInstanceValue = 1; // 지역 멤버
    
          public void print() {
              // 자신의 멤버에 접근
              System.out.println(innerInstanceValue);
              // 외부 클래스의 인스턴스 멤버에 접근 가능, private도 접근 가능
              System.out.println(outInstanceValue);
              // 외부 클래스의 멤버에는 접근 가능. private도 접근 가능
              System.out.println(InnerOuter.outClassValue);
          }
      }
    }
    
    • 바깥 클래스의 인스턴스 멤버와 클래스 멤버에 접근할 수 있다.
  • private 접근 제어자
    • private 접근 제어자는 같은 클래스 안에 있을 때만 접근 가능
    • 내부 클래스는 바깥 클래스와 같은 클래스 안에 있어서 바깥 클래스의 private 접근 제어자에 접근 할 수 있다.
  • 내부 클래스는 바깥 클래스의 인스턴스 정보를 알아야 생성 가능
    • new 바깥클래스의 인스턴스 참조.내부클래스()
    public class InnerOuterMain {
      public static void main(String[] args) {
          InnerOuter outer = new InnerOuter();
          InnerOuter.Inner inner = outer.new Inner();
          inner.print();
    
          System.out.println("innerClass = " + inner.getClass());
      }
    }
    
    • outer.new Inner()에서 outer는 바깥 클래스의 인스턴스 참조를 가진다.
      • 생성된 내부 클래스는 바깥 클래스의 인스턴스 내부에 생성된다.
      • 바깥 클래스의 인스턴스가 먼저 생성되어야지 내부 클래스의 인스턴스를 생성할 수 있다.


image

  • 실제로 내부 클래스가 바깥 클래스의 내부에 생성되는 것은 아니다.
  • 내부 인스턴스는 바깥 인스턴스의 참조를 보관한다. 이 참조를 통해 바깥 인스턴스의 멤버에 접근할 수 있다.

  • 내부 클래스의 생성
    • 바깥 클래스에서 내부 클래스의 인스턴스를 생성할 때는 바깥 클래스의 이름을 생략할 수 있다.
      • new Engine();
    • 내부 클래스의 인스턴스는 바깥 클래스의 인스턴스를 자동 참조한다.

같은 이름의 바깥 변수 접근

  • 섀도잉(Shadowing) : 다른 변수들을 가려서 보이지 않게 하는 것
    public class ShadowingMain {
      public int value = 1;
    
      class Inner {
          public int value = 2;
    
          void go() {
              int value = 3;
              System.out.println("value = " + value); // 3
              System.out.println("this.value = " + this.value); // 2
              System.out.println("ShadowingMain.value = " + ShadowingMain.this.value); // 1
          }
      }
    } 
    
    • 변수의 이름이 같을 때는 대부분 더 가깝거나 구체적인 것이 우선권을 가진다.
      1. 메서드 go()에서는 지역변수인 value가 가장 우선순위가 높다.
      2. this.value는 내부 클래스의 인스턴스에 접근한다.
      3. 바깥클래스.this는 바깥 클래스의 인스턴스에 접근한다.




김영한의 실전 자바 중급 1편을 참고하였습니다

© 2021. All rights reserved.

yaejinkong의 블로그