본문 바로가기

Language/JAVA

[JAVA]내부 클래스(Inner Class)

해당 게시글의 내용은 '김영한의 실전 자바 - 중급편' 을 기반으로 작성되었으며, 제가 학습한 내용을 토대로 다시 작성하게 되었습니다.

내부 클래스

package chapter08.inner;

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(outClassValue);
        }
    }

}
  • 내부 클래스는 정적 중첩 클래스와 다르게 class 앞에 static이 붙지 않습니다. 인스턴스의 멤버가 됩니다.
  • 내부 클래스는 자신의 멤버, 바깥 클래스의 인스턴스 멤버, 바깥 클래스의 클래스 멤버에 접근할 수 있습니다.
  • private 접근 제어자는 같은 클래스 안에 있을 때만 접근할 수 있습니다. 내부 클래스 역시 바깥 클래스와 같은 클래스 안에 있기 때문에 접근할 수 있는 것입니다.
package chapter08.inner;

public class InnerOuterMain {
    public static void main(String[] args) {
        InnerOuter outer = new InnerOuter();
        InnerOuter.Inner inner = outer.new Inner();
        inner.print();

        System.out.println("inner Class = "+inner.getClass());
    }
}

/*
[실행 결과]

1
2
3
innerClass = class nested.inner.InnerOuter$Inner
*/
  • 내부 클래스 인스턴스 생성은 정적 중첩 클래스의 인스턴스 생성 방법과는 조금 다릅니다.
  • 바깥 클래스의 인스턴스를 생성해야 내부 클래스의 인스턴스를 생성할 수 있습니다.
  • 내부 클래스는 바깥 클래스의 인스턴스 참조.new 내부클래스()로 생성할 수 있습니다.
  • outer.new Inner()로 생성한 내부 클래스는 개념상 바깥 클래스의 인스턴스 내부에 생성됩니다.

 

내부 클래스의 생성

[개념]

  • 바깥 클래스의 인스턴스(x001) 내부에서 내부 클래스의 인스턴스(x002)가 생성됩니다.
  • 내부 인스턴스는 바깥 인스턴스를 알기 때문에 바깥 인스턴스 멤버에 접근할 수 있습니다.

[실제]

  • 실제로는 내부 인스턴스가 바깥 인스턴스 안에 생성되는 것은 아닙니다. 그냥 개념 상 그렇게 이해하시는게 편하실 겁니다.
  • 실제로 내부 인스턴스는 바깥 인스턴스의 참조를 보관합니다. 이 참조를 통해 바깥 인스턴스의 멤버에 접근할 수 있습니다.

 

내부 클래스 활용

[활용 전]

//Car 에서만 사용
public class Engine {
    private Car car;
    public Engine(Car car){
        this.car = car;
    }
    public void start(){
        System.out.println("충전 레벨 확인: "+ car.getChargelevel());
        System.out.println(car.getModel() + "의 엔진을 구동합니다.");
    }

}
  • Engine 클래스는 Car 클래스에서만 사용합니다.
  • 엔진을 시작하기 위해선 차의 충전 레벨과 차량의 이름이 필요합니다. 따라서 생성자를 통해 Car 인스턴스의 참조를 보관합니다.
public class Car {
    private String model;
    private int chargelevel;
    private Engine engine;

    public Car(String model, int chargelevel){
        this.model = model;
        this.chargelevel = chargelevel;
        this.engine = new Engine(this);
    }


    //Engine에서만 사용하는 메서드
    public String getModel() {
        return model;
    }

    //Engine에서만 사용하는 메서드
    public int getChargelevel() {
        return chargelevel;
    }

    public void start(){
        engine.start();
        System.out.println(model+"시작 완료");
    }
}
  • Car 클래스에선 Engine 클래스에 필요한 메서드(getModel(), getChargeLevel())를 제공해야 합니다.
  • 결과적으로 Car에선 Engine에서만 사용하는 기능을 위해 메서드를 추가하여 노출시켜야 합니다.
package chapter08.inner.ex1;

public class CarMain {
    public static void main(String[] args) {
        Car myCar = new Car("Model Y", 100);
        myCar.start();
    }
}

/*
[실행 결과]

충전 레벨 확인: 100
Model Y의 엔진을 구동합니다.
Model Y 시작 완료
*/

[활용 후]

public class Car {
    private String model;
    private int chargelevel;
    private Engine engine;

    public Car(String model, int chargelevel){
        this.model = model;
        this.chargelevel = chargelevel;
        this.engine = new Engine();
    }

    public void start(){
        engine.start();
        System.out.println(model+"시작 완료");
    }
    
    private class Engine {
        public void start(){
            System.out.println("충전 레벨 확인: "+ chargelevel);
            System.out.println(model + " 의 엔진을 구동합니다.");
        }

    }
}
  • 어차피 Engine 클래스는 Car 클래스 안에서만 사용되기 때문에 Car 클래스에 내부 클래스로 선언해 주었습니다.
  • 리팩토링 전과 비교해 보면 getChargelevel(), getModel() 호출이 아닌 chargelevel, model 인스턴스 변수에 직접 접근할 수 있게 되었습니다.
  • 바깥 클래스(Car.java)에서 내부 클래스(Engine.java)의 인스턴스를 생성할 때는 바깥 클래스(Car.java)의 이름을 생략할 수 있습니다. (new Engine())
  • 내부 클래스의 인스턴스는 자신을 생성한 바깥 클래스의 인스턴스를 자동으로 참조합니다. 즉, Engine 인스턴스는 자신을 생성한 바깥의 Car 인스턴스를 자동으로 참조하게 됩니다.
public class CarMain {
    public static void main(String[] args) {
        Car myCar = new Car("Model Y", 100);
        myCar.start();
    }
}

/*
[실행 결과]

충전 레벨 확인: 100
Model Y의 엔진을 구동합니다.
Model Y 시작 완료
*/

[리팩토링 전 문제 사항]

  • Car 클래스는 Engine 클래스에서만 필요한 메서드를 제공해야 합니다.
  • Engine 에서만 사용하는 기능을 위해 메서드를 추가하여 모델 이름과 충전 레벨을 외부에 노출해야 합니다.

이러한 점들은 Car 클래스의 정보를 외부에 노출시키는 것이기 때문에 캡슐화를 떨어뜨립니다.

 

 

같은 이름의 바깥 변수 접근

package chapter08;

public class ShadowingMain {
    public int value = 1;

    class Inner {
        public int value = 2;
        void go() {
            int value = 3;
            System.out.println("value = " + value);

            //해당 인스턴스에 대한 value
            System.out.println("this.value = " + this.value);
            System.out.println("ShadowingMain.value = " + ShadowingMain.this.value);
        }
    }

    public static void main(String[] args) {
        ShadowingMain main = new ShadowingMain();
        Inner inner = main.new Inner();

        inner.go();
    }
}

go()의 경우 지역 변수인 value가 가장 가깝기 때문에 3을 출력하게 됩니다. 즉, Inner의 valueShadowingMain의 value는 보이지 않게 됩니다.

이처럼 다른 변수들을 가려서 보이지 않게 하는 것을 섀도잉(Shadowing)이라고 합니다.

 

다른 변수를 가리더라도 인스턴스의 참조를 사용하면 외부 변수에 접근할 수 있습니다.

this.value → 내부 클래스의 인스턴스(Inner의 value, 2)

바깥 클래스 이름.this.value → 바깥 클래스의 인스턴스(ShadowingMain의 value, 1)


관련 포스팅

1. [JAVA]중첩/내부 클래스 (1/4)

2. [JAVA]내부 클래스 (2/4)

3. [JAVA]지역 클래스 (3/4)

4. [JAVA]익명 클래스 (4/4)

'Language > JAVA' 카테고리의 다른 글

[JAVA]익명 클래스(Anonymous Class)  (0) 2024.09.24
[JAVA]지역 클래스(Local Class)  (0) 2024.09.24
[JAVA]중첩/내부 클래스  (0) 2024.09.23
[JAVA]static 파헤치기  (0) 2024.09.23
[JAVA]Reflection??  (0) 2024.09.13