Java

중첩 class

밍구밍구밍 2024. 4. 1. 14:34

01_ 중첩 클래스, 내부 클래스

- 클래스 내부에 또 다른 클래스를 정의 하는 것

- 중첩 클래스는 4종류가 있고 크게 두 가지로 분류

* static : 정적 중첩 클래스

* non static : 내부,지역,익명 클래스

 

1) 중첩 클래스의 선언 위치

2) 중첩과 내부의 개념

- 중첩 (Nested) : 나의 안에 있지만 내것이 아닌 것

ex) 큰 나무 상자안에 다른 나무 상자가 들어 있는 것 (두 상자 내부에 존재하는 요소가 다르다!!)

 

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

ex) 나의 심장,간,콩팥은 나의 내부를 구성 하는 요소이다.

 

※ 중첩과 내부를 구분하는 기준은 인스턴스(같은 인스턴스 or 다른 인스턴스)에 있다.

- 정적 중첩 클래스 인스턴스 소속 x

- 내부 클래스는 바깥 클래스 구성 함 인스턴스 소속 o

 

2-1) 중첩 클래스

- static 이 붙는다

- 바깥 클래스의 인스턴스에 소속 되지 않음

 

ex ) NestedOuter (바깥 클래스) 와 Nested (정적 중첩 클래스) 를 생성 후, main() 메서드 생성 시

- main() 에서 중첩 클래스 Nested 접근:  NestedOuter(바깥).Nested(중첩) nested (객체)  = new .NestedOuter.Nested(); 와 같이 바깥 클래스.중첩클래스 로 접근 할 수 있다.

( Nested nested = new Nested(); 같이 단독으로는 객체 생성 불가능 !!) 무조건 바깥 매서드 . 붙여야함!!

 

중첩 클래스 생성 방법

code 1-1)

package nested.nested;

public class NestedOuter {

    private static int outClassValue = 3; // static 필드 생성
    
    private int outInstanceValue = 2;     // instance value 멤버 생성

    static class Nested { // 정적 중첩 클래스 Nested 생성
        private int nestedInstanceValue = 1; // 멤버 변수 생성

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

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

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

        }
    }

}

(※ 정리 : NestedOuterNested 는 아무런 관계가 없다 ( 단지, 두 클래스 모두 private 에 접근 할 수 있는 점) )

 

** 단, 나의 클래스 내에서 사용되는 중첩 클래스의 경우 main() 객체 생성 시 바깥클래스.중첩클래스 안해도 된다

그냥 중첩클래스만 선언

 

code 1-2) 바깥 클래스: Network / 중첩 클래스: NetworkMessage

package nested.nested.ex2;

public class Network {
    // NetworkMessage 클래스를 Network 클래스 안에 중첩해서 생성 (ex1 리팩토링)
    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);
        }
    }
}

 

>> 중첩 클래스 용도 : 자신이 소속된 바깥 클래스 안에서 사용되는 것.

자신이 소속된 바깥 클래스 외부에서 중첩 클래스를 사용할 경우, 해당 중첩클래스를 바깥으로 빼야 적절함.

 

2-2) 내부 클래스

- static 이 붙지 않는다

- 바깥 클래스의 인스턴스에 소속 됨

- 내부 클래스's 특징 (포함 관계 : 내부클래스 < 지역 클래스 < 익명 클래스)

> 내부 클래스 (Inner Class) : 바깥 클래스의 인스턴스 멤버에 접근

> 지역 클래스 (Local Class) : 내부 클래스의 특징 + 지역 변수에 접근

> 익명 클래스 (Anonymous Class) : 지역 클래스의 특징 + 클래스의 이름이 없는 특별한 클래스

 

내부 클래스 생성 방법

code 1-1 ) 바깥 클래스 : InnerOuter / 내부 클래스 : Inner

package nested.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);

            // 외부 클래스 클래스멤버에 접근 가능
            System.out.println(outClassValue);
        }
    }
}

 

code 1-2) 내부클래스 생성 방법

package nested.inner;

public class InnerOuterMain {

    public static void main(String[] args) {

        // 내부 클래스 접근 방법
        InnerOuter outer = new InnerOuter(); // step1. 바깥 클래스 참조 값을 먼저 생성
        InnerOuter.Inner inner = outer.new Inner(); // step2. 해당 바깥 클래스의 내부 클래스 참조 생성
    }
}

 

** main() 메서드에서 내부 클래스 생성 방법

1. 먼저 바깥 클래스 InnerOuter의 참조값을 불러온다. new InnerOuter(); + ( Ctrl + Alt + v) 

(outer 로 객체 이름 생성) 

2. 바깥 클래스 참조 값을 알게되야 내부 클래스인 Inner 를 생성 할 수 있다. outer.new Inner(); + ( Ctrl + Alt + v)

정리 : 바깥 클래스의 참조값을 알아야 내부 클래스를 생성/사용 가능 !!

(>내부 클래스가 바깥 클래스의 인스턴스를 참조 하기 때문)

 

※ 개념 : 바깥클래스의 인스턴스 내부에서 내부 클래스의 인스턴스가 생성 된다

내부 인스턴스는 바깥 인스턴스를 알기 떄문에 바깥 인스턴스 멤버에 접근 가능한 것이다.

 

03_ 쉐도윙 (Shadowing)

package nested;

public class ShadowingMain {

    // 마지막으로 내부클래스 바깥에 있는 value = 1
    public int value = 1;

    class Inner { // 두번째로 근접한 value = 2
        public int value = 2;

        void go() { // 내부 클래스와 가장 근접한 value = 3
            int value = 3;
            System.out.println("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();
        
    }
}

- 인스턴스 값 호출 시, 가장 근접한 값 부터 .(dot) 으로 접근 한다.

>> value 

>> this.value

>> ShawdowingMain.this.value

 

 

04_ 지역 클래스

- 지역 클래스는 지역 변수처럼 코드 블럭 안에 클래스를 선언 한다.- 지역 클래스는 지역 변수에 접근 가능하다.

 

1) 지역 변수 캡처

 

- 변수의 생명 주기

 

지역 변수는 값이 변경 되면 안된다 (사실상 final)

 

05_ 익명 클래스

익명 클래스 생성 예)

public interface Printer {

    void print();
}

 

package nested.anonymous;

import nested.local.Printer;

public class AnonymousOuter {

    private int outInstanceVar = 3;

    public void process(int paramVar) {
        int localVar = 1; // 지역 변수

        Printer printer = new Printer() { // 익명 클래스 : 선언과 동시에 생성
            int value = 0;

            @Override
            public void print() {
                System.out.println("value = " + value);
                System.out.println("localVar = " + localVar);
                System.out.println("paramVar = " + paramVar);
                System.out.println("outInstanceVar = " + outInstanceVar);

            }
        };

        printer.print();
        System.out.println("printer.class = " + printer.getClass());

    }

    public static void main(String[] args) {
        AnonymousOuter main = new AnonymousOuter();
        main.process(2);

    }
}

 

위의 익명클래스 선언하는 코드 라인만 보면 Printer 인터페이스를 생성하는 것 처럼 보이지만 사실

Printer 인터페이스를 구현한 익명클래스를 생성하는 것

 

특징 :

- 익명클래스는 클래스의 body를 정의하면서 동시에 생성 가능

- 익명클래스는 부모 클래스를 상속 받거나, 인터페이스를 구현해야 한다. (익명 클래스 사용 시 상위 클래스 or 인터페이스 필요)

- 익명클래스는 생성자를 가질 수 없음 (이름이 없기 때문)

- 익명클래스는 바깥클래스 + $ +n 으로 정의됨 ($1, $2, $3 .... )

 

지역클래스로 만들면 main() 에서 생성자 생성할 필요 없어짐code) 

package nested.anonymous.ex;

import java.util.Random;

public class Ex1RefMain3 {

    
    public static void hello(Process process) {
        System.out.println("프로그램 시작");

        process.run();

        System.out.println("프로그램 종료");
    }


    public static void main(String[] args) {
        Process dice = new Process() { // 익명 클래스 생성

            @Override
            public void run() {
                int randomValue = new Random().nextInt(6) + 1;
                System.out.println("주사위 = " + randomValue);
            }
        };

        Process sum = new Process() { // 익명 클래스 생성

            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    System.out.println("i = " + i);
                }
            }
        };

        System.out.println("Hello 실행");
        hello(dice); // 바로 run() 메서드를 통해 dice 와 hello 호출 가능
        hello(sum);
    }
}

 

>> 리팩토링 (익명클래스를 hello 메서드 내부로 보냄 = 인수로 전달)

package nested.anonymous.ex;

import java.util.Random;

public class Ex1RefMain4 {


    public static void hello(Process process) {
        System.out.println("프로그램 시작");

        process.run();

        System.out.println("프로그램 종료");
    }


    public static void main(String[] args) {

        System.out.println("Hello 실행");
        hello(new Process() { // 익명 클래스를 hello 에다가 넣어버림

            @Override
            public void run() {
                int randomValue = new Random().nextInt(6) + 1;
                System.out.println("주사위 = " + randomValue);
            }
        });

        hello(new Process() { // 익명 클래스를 hello 에다가 넣어버림

            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    System.out.println("i = " + i);
                }
            }
        });
    }
}