중첩 클래스, 내부 클래스 1

황상익·2024년 5월 20일

Inflearn JAVA

목록 보기
30/61

중첩 클래스, 내부클래스란?

for문을 중첩하는 것을 중첩 for문이라고 한다.

for (...) {
 //중첩 for문
 for (...) {
 }
}

클래스 안에 클래스를 중첩해서 정의할 수 있는데 중첩 클래스라고 한다.

class Outer {
 ...
 //중첩 클래스
 class Nested {
 ...
 }
}

중첩 클래스 종류

중첩 클래스는 -> 정적, 내부 (내부, 지역, 익명)
변수의 위치 -> 정적 변수, 인스턴스, 지역
중첩 클래스 위치 선언
-> 정적 중첩 클래스 -> 정적 변수와 위치 동일
-> 내부 클래스 -> 인스턴스 변수와 같은 위치
-> 지역 클래스 -> 지역 변수와 같은 위치

class Outer {
 ...
 //정적 중첩 클래스
 static class StaticNested {
 ...
 }
 //내부 클래스
 class Inner {
 ...
 }
}

정적 중첩 클래스는 정적 변수와 같이 앞에 static
내부 클래스는 인스턴스 변수와 같이 앞에 static

class Outer {
 public void process() {
 //지역 변수
 int lcoalVar = 0;
 //지역 클래스
 class Local {...}
 Local local = new Local();
 }
}

지역 클래스는 코드 블럭 안에서 클래스 정의
익명 클래스는 지역 클래스의 특별한 버전

중첩 : 내부에 위치하거나 포함되는 구조적 관계
내부 : 내부에 있는 나를 구성하는 요소

중첩은 안에 있지만 내것이 아닌 것을 말한다.
단순히 위치만 안에 있는 것. 반면에 내부는 나의 내부에서 나를 구성하는 요소

중첩 클래스는 바깥 클래스의 안에 있지만 바깥 클래스와 관계 없는 전혀 다른 클래스
내부 클래스는 바깥 클래스의 내부에 있으면서 바깥 클래스를 구성하는 요소

중첩과 내부를 분류하는 핵심은 바깥 클래스 입장에서 볼때는 안에 있는 클래스가 나의 인스턴스 소속이 되는가 되지 않는가 차이

중첩과 내부를 분류하는 핵심은 바깥 클래스 입장에서 볼때 안에 있는 클래스가 나의 인스턴스 소속이 되는가 되지 않는가 차이

  • 정적 중첩 클래스는 바깥 클래스와 전혀 다른 클래스 -> 바깥 클래스 소속 X
  • 내부 클래스는 바깥 클래스를 구성하는 요소

정적 중첩 클래스
static 이 붙는다.
바깥 클래스의 인스턴스에 소속되지 않는다.

내부 클래스
static 이 붙지 않는다.
바깥 클래스의 인스턴스에 소속된다

내부 클래스 종류

  • 내부 클래스 : 바깥 클래스의 인스턴스의 멤버에 접근
  • 지역 클래스 : 내부 클래스의 특징 + 지역 변수에 접근
  • 익명 클래스 : 지역 클래스 특징 + 클래스의 이름이 없는 특별한 클래스

중첩 클래스: 정적 중첩 클래스 + 내부 클래스 종류 모두 포함
정적 중첩 클래스: 정적 중첩 클래스를 말함
내부 클래스: 내부 클래스, 지역 클래스, 익명 클래스를 포함해서 말함

중첩 클래스는 언제 사용 ??

내부 클래스를 포함된 모든 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용, 둘이 아주 긴밀하게 연결 된 경우에만 사용

중첩 클래스를 사용하는 이유

  • 논리적 구조화 : 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화. 다른 곳에 사용 될 필요 없는 경우, 중첩 클래스가 외부에 노출 X
  • 캡슐화 : 중첩 클래스 바깥 클래스의 private 멤버에 접근 가능. 둘을 긴밀하게 연결하고 불필요한 public 메서드를 제거

정적 중첩 클래스

public class NestedOuter {
    private static int outClassVal = 3;
    private int outInstanceVal = 2;

    static class Nested{
        private int nestedInstanceVal = 1;

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

            //바깥 클래스의 인스턴스 멤버에는 접근 할 수 X
            //System.out.println(outInstanceVal);
            //static이기 떄문에 접근 할 수 없음

            //바깥 클래스의 클래스 멤버에는 접근할 수 있다.
            //클래스 영역에 있다, private이어도 접근 가능
            System.out.println(NestedOuter.outClassVal);
        }
    }
}

정적 중첩 클래스 앞에는 static
정적 중첩 클래스는
자신의 멤버에는 당연히 접근
바깥 클래스의 인스턴스 멤버에는 접근 X
바깥 클래스의 클래스 멤버에는 접근 O

private 접근 제어자
중첩 클래스도 바깥 클래스와 같은 클래스 안에 있다. 중첩 클래스는 바깥 클래스의 private 접근 제어자를 접근 할 수 있다.

public class NestedOuterMain {
    public static void main(String[] args) {
        NestedOuter outer = new NestedOuter();
        NestedOuter.Nested nested = new NestedOuter.Nested();
        nested.print();
        System.out.println("nested.getClass() = " + nested.getClass());
    }
}

정적 중첩 클래스는 new 바깥클래스.중첩클래스로 생성 가능
new NestedOuter() 로 만든 바깥 클래스의 인스턴스와 new NestedOuter.Nested() 로 만든
정적 중첩 클래스의 인스턴스는 서로 아무 관계가 없는 인스턴스이다. 단지 클래스 구조상 중첩해 두었을 뿐이다.

바깥 클래스의 멤버에 접근

정적 중첩 클래스의 활용

public class Network {

    public void sendMessage(String text){
        NetworkMessage message = new NetworkMessage(text);
        message.print();
    }
}
public class NetworkMain {
    public static void main(String[] args) {
        Network network = new Network();
        network.sendMessage("Hello");
    }
}
public class NetworkMessage {
    private String content;

    public NetworkMessage(String content) {
        this.content = content;
    }

    public void print(){
        System.out.println(content);
    }
}

정적 중첩 클래스로 리펙토링 후

public class Network {

    public void sendMessage(String text){
        NetworkMessage message = new NetworkMessage(text);
        message.print();
    }

    private static class NetworkMessage {
        private String content;

        public NetworkMessage(String content) {
            this.content = content;
        }

        public void print(){
            System.out.println(content);
        }
    }
}
public class NetworkMain {
    public static void main(String[] args) {
        Network network = new Network();
        network.sendMessage("Hello");
    }
}

중첩 클래스의 접근

바깥클래스.중첩클래스 로 접근 해야 한다

NestedOuter.Nested nested = new NestedOuter.Nested();

나의 클래스에 포함된 중첩 클래스에 접근할 때는 바깥 클래스 이름을 적지 않아도 ok

public class Network {
 public void sendMessage(String text) {
 NetworkMessage networkMessage = new NetworkMessage(text);
 }
 private static class NetworkMessage {...}
}

내부 클래스

정적 중첩 클래스는 바깥 클래스와 서로 관계 X. 내부 클래스는 바깥 클래스의 인스턴스를 이루는 요소가 된다.

public class InnerOuter {
    private static int outClassVal = 3;
    private int outInstanceVal = 2;

    class Inner {
        private int innerInstanceVal = 1;

        public void print(){
            //자기 자신 접근
            System.out.println(innerInstanceVal);

            //외부 클래스의 인스턴스 접근
            System.out.println(outInstanceVal);
        }
    }

}

내부 클래스는 앞에 static 이 붙지 않는다. 쉽게 이야기해서 인스턴스 멤버가 된다.
내부 클래스는

  • 자신의 멤버에는 당연히 접근할 수 있다.
  • 바깥 클래스의 인스턴스 멤버에 접근할 수 있다.
  • 바깥 클래스의 클래스 멤버에 접근할 수 있다.

private 접근 제어자

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.getClass() = " + inner.getClass());
    }
}

내부 클래스는 바깥 클래스의 인스턴스 소속, 바깥 클래스의 인스턴스 정보를 알아야 생성 가능

new 바깥클래스의 인스턴스 참조.내부클래스() 로 생성할 수 있다.

  • 내부 클래스는 바깥 클래스의 인스턴스 소속이 되어야 함. 내부 클래스를 생성할떄 바깥 클래스의 인스턴스 참조 필요
  • outer.new Inner()에서 outer는 바깥 클래스의 인스턴스 참조를 갖는다.
  • outer.new Inner() 생성한 내부 클래스는 개념상 바깥 클래스의 인스턴스 내부에 생성
  • 바깥 클래스의 인스턴스를 먼저 생성해야 내부 클래스의 인스턴스를 생성할 수 있다.

내부 클래스 생성

내부 인스턴스는 바깥 인스턴스를 알기 때문에 인스턴스의 멤버에 접근 가능


실제로 내부 인스턴스가 바깥 인스턴스 안에 생성 되는 것은 X
개념상 인스턴스 안에 생성 된다고 볼수 있다.

내부 인스턴스는 바깥 인스턴스의 참조를 보관.

내부 클래스 활용

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());
    }
}
public class Car {
    private String model;
    private int chargeLevel;
    private Engine engine;

    public Car(String model, int chargeLevel) {
        this.model = model;
        this.chargeLevel = chargeLevel;
        //engine을 넘길때 car 넘긴다.
        this.engine = new Engine(this);
    }

    public String getModel() {
        return model;
    }

    public int getChargeLevel() {
        return chargeLevel;
    }

    public void start(){
        engine.start();
        System.out.println(model);
    }
}
public class CarMain {
    public static void main(String[] args) {
        Car car = new Car("X", 100);
        car.start();
    }
}

리펙토링 후

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

    public Car(String model, int chargeLevel) {
        this.model = model;
        this.chargeLevel = chargeLevel;
        //engine을 넘길때 car 넘긴다.
        this.engine = new Engine(this);
    }

    public String getModel() {
        return model;
    }

    public int getChargeLevel() {
        return chargeLevel;
    }

    public void start(){
        engine.start();
        System.out.println(model);
    }
}
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());
    }
}
public class CarMain {
    public static void main(String[] args) {
        Car car = new Car("X", 100);
        car.start();
    }
}

리팩토링 전에는 결과적으로 모델 이름과 충전 레벨을 외부에 노출했다. 이것은 불필요한 Car 클래스의 정보들이 추가로 외부에 노출되는 것이기 때문에 캡슐화를 떨어뜨린다.

리팩토링 후에는 getModel() , getChargeLevel() 과 같은 메서드를 모두 제거했다. 결과적으로 꼭 필요한 메서드만 외부에 노출함으로써 Car 의 캡슐화를 더 높일 수 있었다

같은 이름의 바깥 변수 접근

public class ShdowingMain {
    public int val = 1;

    class Inner{
        public int val = 2;

        void go(){
            int val = 3;
            System.out.println("val = " + val);
            //나의 인스턴스 val
            System.out.println("this.val = " + this.val);
            //바깥 클래스 접근
            System.out.println(ShdowingMain.this.val);
        }
    }

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

변수 이름이 동일하기에 어떤 변수를 우선 사용할지 순위를 정해야 함.
프로그래밍에서 우선순위는 대부분 더 가깝거나 구체적인것 우선 순위.

다른 변수를 가리더라도 인스턴스의 참조를 사용하면 외부 변수에 접근 가능
this.value 는 내부 클래스의 인스턴스에 접근하고, 바깥클래스이름.this 는 바깥 클래스의 인스턴스에 접근할 수 있다

profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글