공부/Java

자바 중첩 클래스, 내부 클래스 복습하기

Stair 2024. 9. 20. 15:53
반응형

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

다음과 같이 for 문 안에 for문을 중첩하는 것을 중첩(Nested) for문이라고 한다

for(){

    for(){

    }

}

 

클래스도 마찬가지로 클래스 안에 클래스를 중첩해서 정의할 수 있는데, 이것을 중첩 클래스(Nested Class)라 한다.

class Outer{

    class Nested{

    }

}

 

중첩 클래스는 정의하는 위치에 따라 다음과 같이 분류한다.

 

중첩 클래스는 총 4가지가 있고, 크게 2가지로 분류할 수 있다.

정적 중첩 클래스

내부클래스 : 내부 클래스, 지역 클래스, 익명 클래스

 

변수 선언 위치

1. 정적 변수(클래스 변수) -> 정적 변수와 같은 위치

2. 인스턴스 변수 -> 인스턴스 변수와 같은 위치

3. 지역 변수 -> 지역 변수와 같이 코드 블럭 안에서 클래스를 정의한다(메서드 안에서)

 

정적 중첩 클래스는 정적 변수와 같이 앞에 static이 붙어있다.

내부 클래스는 인스턴스 변수와 같이 앞에 static이 붙어있지 않다.

 

중첩과 내부라는 단어는 무슨 차이가 있는 걸까?

중첩(Nested) : 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계

내부(Inner) : 나의 내부에 있는 나를 구성하는 요소

 

쉽게 이야기해서 여기서 의미하는 중첩은 나의 안에 있지만 내 것이 아닌것을 말한다. 반면 여기서 내부는 나의 내부에서 나를 구성하는 요소를 말한다.

 

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

 

 

중첩 클래스의 사용 이유 : 내부 클래스를 포함한 모든 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나 둘이 아주 긴밀하게 연결되어 있는 경우에만 사용해야 한다. 외부의 여러 클래스가 특정 중첩 클래스를 사용한다면 밖으로 빼내야한다.

 

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

1. 논리적 그룹화

2. 캡슐화

 

 

 

 

우선 정적 중첩 클래스를 만들어보자

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);

            //바깥 클래스의 클래스 멤버에는 접근할 수 있다. private도 접근 가능
            System.out.println(outClassValue);

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

 

정적 중첩 클래스 앞에는 static이 붙는다

1. 자신의 멤버에는 당연히 접근할 수 있다.

2. 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.

3. 바깥 클래스의 클래스 멤버에는 접근할 수 있다.(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());

    }
}

1
3
nested.getClass() = class nested.nested.NestedOuter$Nested

 

중첩 클래스는 바깥 클래스.중접 클래스로 접근할 수 있다.

 

정적 중첩 클래스는 바깥 클래스의 정적 필드에는 접근할 수 있지만 바깥 클래스가 만든 인스턴스 필드에는 바로 접근할 수 없다.

 

정적 중첩 클래스는 사실 다른 클래스를 그냥 중첩해 둔 것일 뿐이다. 쉽게 이야기해서 둘은 아무런 관계가 없다.

private 접근 제어자에 접근할 수 있냐 없냐 정도이다.

 

 

그럼 정적 중첩 클래스는 왜 사용될까??

아래와 같은 코드를 보자

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 networkMessage = new NetworkMessage(text);
        networkMessage.print();
    }
}
public class NetworkMain {

    public static void main(String[] args) {
        Network network = new Network();
        network.sendMessage("Hello Java!");
    }
}

 

처음 보는 개발자는 아마 두 클래스를 모두 확인해볼 것이다. 그리고 어떤 것을 사용해야하는지 로직을 타보고 난 이후 Network만 사용하는 것을 뒤늦게 알아차릴 것이다.

이럴 때 정적 중첩 클래스를 사용하여 클래스를 하나로 압축시킬 수 있다.

public class Network {

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

    private static class NetworkMessage {

        private String content;

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

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

}

 

NetworkMessage를 Network 안에 중첩해서 만들었고, private로 막아서 외부에서 접근하는 것을 막았다.

public class NetworkMain {
    public static void main(String[] args) {
        Network network = new Network();
        network.sendMessage("Hello Java!");
    }
}

 

이해가 쉬워지고 패키지에 번거로운 클래스가 빠졌다. 추가로 NetworkMessage가 중첩 클래스에 private 접근 제어자로 되어 있는 것을 보고, Network 내부에서만 단독으로 사용하는 클래스라고 인지할 수 있다.

 

만약 외부에서 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 = " + innerInstanceValue);
            
            //외부 클래스의 인스턴스 멤버에 접근 가능, private도 접근 가능
            System.out.println("outInstanceValue = " + outInstanceValue);

            //외부 클래스의 클래스 멤버에 접근 가능, private도 접근 가능
            System.out.println("outClassValue = " + outClassValue);
        }
    }
}

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

1. 자신의 멤버에는 당연히 접근할 수 있다.

2. 바깥 클래스의 인스턴스 멤버에 접근할 수 있다.

3. 바깥 클래스의 클래스 멤버에 접근할 수 있다.

 

내부 클래스도 바깥 클래스와 같은 클래스 안에 있기 때문에 내부 클래스는 바깥 클래스의 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());
    }
}

innerInstanceValue = 1
outInstanceValue = 2
outClassValue = 3
inner.getClass() = class nested.nested.inner.InnerOuter$Inner

 

 

내부 클래스는 바깥 클래스의 인스턴스에 소속된다. 따라서 바깥 클래스의 인스턴스 정보를 알아야 생성할 수 있다.

outer.new Inner()를 통해 outer의 참조값을 알 수 있는 인스턴스 객체가 생성된다.

 

 

같은 이름의 바깥 변수에 접근은 어떻게 할까?

public class ShadowingMain {

    public int value = 1;

    class Inner{
        public int value = 2;

        void go(){
            int value = 3;
            System.out.println("value = " + value);
            System.out.println("this.value = " + this.value);
            System.out.println("ShadowingMain.this.value = " + ShadowingMain.this.value);
        }
    }

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

 

프로그래밍에서 우선순위는 대부분 더 가깝거나, 더 구체적인 것이 우선권을 가진다. 쉽게 이야기해서 사람이 직관적으로 이해하기 쉬운 방향으로 우선순위를 설계한다.

 

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

 

하지만 프로그래밍에서 가장 중요한 것은 명확성이다. 이렇게 이름이 같은 경우는 처음부터 이름을 서로 다르게 지어서 명확하게 구분하는 것이 더 나은 방법이다

 

반응형

'공부 > Java' 카테고리의 다른 글

자바 예외처리 복습하기  (0) 2024.09.22
자바 지역클래스 복습하기  (0) 2024.09.21
자바 열거형 ENUM 복습하기  (0) 2024.09.20
자바 String 클래스 복습하기  (1) 2024.09.19
자바 불변 객체 복습하기  (1) 2024.09.18