2023.02.19 자바 리뷰

9. 중첩 선언 및 익명 개체

9.1 중첩 클래스

객체 지향 프로그램에서 클래스는 긴밀한 관계를 통해 서로 상호 작용합니다.


하나의 클래스가 여러 클래스와 연관되어 있을 경우에는 별도로 선언하는 것이 좋지만 특정 클래스가 하나만 연관되어 있을 때는 중첩 클래스로 선언하는 것이 유지 관리에 도움이 되는 경우가 많습니다.


중첩 클래스는 클래스 내부에 선언된 클래스입니다.

중첩 클래스를 사용하면 클래스의 멤버를 쉽게 사용할 수 있고 중첩 클래스 관계를 외부에서 숨겨 코드 복잡성을 줄일 수 있다는 장점이 있습니다.


중첩 클래스가 선언된 위치에 따라 두 가지 유형으로 분류됩니다.


클래스의 멤버로 선언된 중첩 클래스를 멤버 클래스라고 하고, 메소드 내부에 선언된 중첩 클래스를 로컬 클래스라고 합니다.

중첩된 클래스도 클래스이므로 컴파일 시 별도의 바이트코드 파일이 생성됩니다.


1. 회원 클래스의 경우
바이트코드 파일의 이름은 A(외부 클래스) $B(멤버 클래스).class가 됩니다.


2. 현지 수업 현황
$1을 포함하는 바이트코드 파일 A(외부 클래스) $1은 B(멤버 클래스).class가 됩니다.

9.2 인스턴스 멤버 클래스

인스턴스 멤버 클래스는 클래스 A의 멤버로 선언된 클래스 B입니다.

액세스 리미터에 따른 액세스 범위는 다음과 같습니다.


공용 B 클래스는 다른 패키지에서 사용할 수 있습니다.


기본 패키지에서만 사용 가능
클래스는 개인 A 클래스 내에서만 사용할 수 있습니다.

클래스 B의 인스턴스 멤버는 주로 클래스 A 내에서 사용되기 때문에 일반적으로 전용 액세스 한정자가 있습니다.


B 객체는 A 클래스 내부 어디에도 생성할 수 없지만 인스턴스 필드 값, 생성자 및 인스턴스 메서드에서 생성할 수 있습니다.


객체 B가 생성되기 전에 객체 A가 존재해야 하기 때문입니다.


객체 A를 생성해야 객체 B를 생성할 수 있습니다.

public class A {
    // 인스턴스 멤버 클래스
    class B {
    }

    // 인스턴스 필드값으로 B객체 대입
    B field = new B();

    // 생성자
    A() {
        B b = new B();
    }

    // 인스턴스 메소드
    void method() {
        B b = new B();
    }
}

public class AExample {
    public static void main(String() args) {
        // A객체 생성
        A a = new A();

        // B객체 생성
        A.B b = a.new B(); 
    }
}

인스턴스 멤버 클래스 B 내부에는 일반 클래스 및 garting 필드에 대한 생성자 선언이 있을 수 있습니다.


Java 17부터 정적 필드와 정적 메서드를 선언할 수 있습니다(거의 쓸모 없음).

public class A {
    //인스턴스 멤버클래스
    class B{
        //인스턴스 필드
        int field1 = 1;

        //정적필드(java 17부터 허용)
        static int field2 = 2;

        //생성자
        B() {
            System.out.println("B-생성자 실행");
        }

        //인스턴스 메소드
        void method1(){
            System.out.println("B-method1 실행");
        }

        //정적메소드(java 17부터 허용)
        static void method2() {
            System.out.println("B-method2 실행");
        }

    }

    // A의 인스턴스 메소드
    void useB() {
        B b = new B();
        System.out.println(b.field1);
        b.method1();

        //B클래스의 정적 필드 및 메소드 사용
        System.out.println(B.field2);
        B.method2();
    }
}

public class Aexample {
    public static void main(String() args) {
        // A객체 생성
        A a = new A();

        //A인스턴스 메소드 호출
        a.useB();

        //A를통해서B의 정적필드 메소드 사용
        A.B.field2 = 1; 
        A.B.method2();
    }
}

9.3 정적 멤버 클래스

정적 멤버 클래스는 static 키워드를 사용하여 클래스 A의 멤버로 선언된 클래스 B입니다.

접근 한정자에 따른 인스턴스 멤버 클래스의 접근 범위는 다음과 같다.


다른 패키지에서 사용 가능한 공개 정적
기본 정적과 같은 패키지에서만
개인 정적은 A 내에서만 사용할 수 있습니다.

클래스 B의 정적 멤버는 클래스 내부에서 사용되지만 클래스 A 외부에서 A와 함께 자주 사용되므로 기본 또는 공개 액세스 제한이 있습니다.


객체 B는 클래스 A의 어느 위치에나 객체를 생성할 수 있습니다.

A 클래스 외부에서 객체 B를 생성하려면 객체 A를 생성하지 않고 클래스 A에 접근한 후 객체 B를 생성하면 된다.


AB b = 새로운 AB();

public class A {
    //인스턴스 멤버 클래스
    static class B{
        //인스턴스 필드
        int field1 = 1;

        //정적필드(java 17부터 허용)
        static int field2 = 2;

        //생성자
        B() {
            System.out.println("B-생성자 실행");
        }

        //인스턴스 메소드
        void method1(){
            System.out.println("B-method1 실행");
        }

        //정적메소드(java 17부터 허용)
        static void method2() {
            System.out.println("B-method2 실행");
        }
    }
}

public class AExample {
    public static void main(String() args) {
        // B객체 생성 및 인스턴스 필드 및 메소드 사용
        A.B b = new A.B();
        System.out.println(b.field1);
        b.method1();

        //b클래스의 정적필드 및 메소드사용
        System.out.println(A.B.field2);
        A.B.method2();
    }
}

9.4 네이티브 클래스

생성자 또는 메서드에서 다음과 같이 선언된 클래스를 로컬 클래스라고 합니다.


네이티브 클래스는 생성자와 메서드가 실행되는 동안에만 개체를 ​​만들 수 있습니다.

public class A {
    //메소드
    void useB() {
        //로컬 클래스
        class B{
            //인스턴스 필드
            int field1 = 1;

            //정적 필드 (자바17~)
            static int field2 = 2;

            //생성자
            B() {
                System.out.println("B-생성자 실행");
            }

            //인스턴스 메소드
            void method1() {
                System.out.println("B-method1 실행");
            }

            void method2() {
                System.out.println("B-method2 실행");
            }
        }

        //로컬 객체 생성
        B b = new B();

        //로컬 객체의 인스턴스 필드와 메소드 사용
        System.out.println(b.field1);
        b.method1();
        System.out.println(b.field2);
        b.method2();
    }
}

public class AExample {
    public static void main(String() args) {
        //A객체 생성
        A a = new A();

        //A메소드 호출
        a.useB();
    }
}

지역 변수가 지역 클래스에서 사용될 때 지역 변수는 최종 속성을 가지므로 값을 읽을 수만 있고 수정할 수 없습니다.


이는 로컬 클래스 내부의 값 변경을 제한하기 때문입니다.


Java 8부터는 명시적으로 final을 붙일 필요는 없지만, 붙여서 final 변수임을 명시하자.

public class A {
    //메소드
    public void method1(int arg) {//final int arg
        //로컬변수
        int var = 1; //final int var = 1;

        //로컬 클래스
        class B {
            //메소드
            void method2() {
                //로컬변수 읽기
                System.out.println("arg: " + arg);
                System.out.println("var: " + var);

                //로컬 변수 수정 불가
                //arg = 3;
                //var = 2;
            }
        }

        //로컬 객체 생성
        B b = new B();
        //로컬 객체 메소드 호출
        b.method2();

        //로컬 변수 수정 불가? <- 변경은 가능한데 변경하면
        //로컬클래스에서 사용못하게됨.
        //arg = 3;
        //var = 2;
    }
}

변수가 로컬 클래스에서 사용되면 최종 변수가 됩니다.

9.5 외부 회원 액세스

중첩 클래스는 외부 클래스와 긴밀한 관계를 유지하면서 외부 클래스의 멤버에 액세스할 수 있습니다.


중첩 클래스 선언 방식에 따라 접근 제한이 있을 수 있습니다.

9.5.1 외부 클래스의 멤버 액세스 제한

정적 멤버 클래스 내부에는 외부 클래스의 필드 및 메서드 사용에 대한 제한이 있습니다.


인스턴스 멤버 클래스 -> 외부 클래스의 모든 필드 및 메소드 /
-> A객체가 먼저 생성되어야 B객체가 생성되기 때문에 다 사용할 수 있을 것 같습니다.


정적 멤버 클래스 -> 외부 클래스의 정적 필드 및 정적 메서드
-> A가 생성되지 않아도 B는 사용할 수 있지만 A가 생성되어야만 사용할 수 있는 인스턴스는 사용할 수 없는 것이 맞습니다.


이는 정적 메서드에서 인스턴스 필드를 사용할 수 없는 것과 같은 이유인 것 같습니다.

public class A {
    //A의 인스턴스 필드와 메소드
    int field1;
    void method1() {}

    //A의 정적필드와 메소드
    static int field2;
    static void method2() {}

    //인스턴스 멤버 클래스
    class B {
        void method() {
            //A의 인스턴스 필드와 메소드 사용
            field1 = 10;
            method1();

            //A의 정적필드와  메소드 사용
            field2 = 10;
            method2();

        }    
    }
    //정적멤버 클래스
    static class C {
        void method() {
            //A의 인스턴스 필드와 메소드 사용
            //field1 = 10; 불가능
            // method1(); 불가능

            //A의 정적필드와  메소드 사용
            field2 = 10; // 가능
            method2(); // 가능
        }
    }
}

9.5.2 외부 클래스에서 객체에 액세스하기

중첩 클래스에서 this는 중첩 클래스의 개체를 나타냅니다.


중첩 클래스에서 외부 클래스의 개체에 액세스하려면 외부 클래스 이름에 추가하면 됩니다.

public class A {
    //A의 인스턴스 필드
    String field = "A-field";

    //A의 인스턴스 메소드
    void method() {
        System.out.println("A-method");
    }

    //인스턴스 멤버 클래스
    class B{
        //B 인스턴스 필드
        String field = "B-field";

        //B 인스턴스 메소드
        void method() {
            System.out.println("B-method");
        }

        //B인스턴스 메소드
        void print() {
            //B객체의 필드와 메소드 사용
            System.out.println(this.field); //B의 필드
            this.method();  //B의 메소드

            //A객체의 필드와 메소드 사용
            System.out.println(A.this.field); //A(바깥)의 필드
            A.this.method();  //A(바깥)의 메소드
        }

    }
    //A의 인스턴스 메소드
    void useB() {
        B b = new B();
        b.print();
    }
}

9.6 중첩된 인터페이스

중첩 인터페이스는 클래스의 멤버로 선언된 인터페이스입니다.


클래스 내부에 인터페이스를 선언하는 이유는 클래스와 친화력이 있는 구현 객체를 생성하기 위함입니다.


외부 액세스를 차단하지 않으려면 public을 연결하고 클래스 A 내부에서만 사용하려면 private을 연결하십시오.
액세스 한정자가 첨부되지 않은 경우 동일한 패키지 내에서만 액세스가 가능합니다.


static을 추가하여 static으로 선언할 수도 있습니다.

public class Button {
    //정적 중첩 인터페이스
    public static interface ClickListener {
        //추상메소드
        void onClick();
    }
}

외부에서 접근할 수 있도록 Button 객체 없이 사용할 수 있는 public static 중첩 인터페이스로 선언합니다.


onClick 메서드는 버튼을 클릭했을 때 호출되는 메서드입니다.

public class Button {
    //정적 중첩 인터페이스
    public static interface ClickListener {
        //추상메소드
        void onClick();
    }

    //필드 
    private ClickListener clickListener;

    //메소드
    public void setClickListener(ClickListener clickListener) {
        this.clickListener = clickListener;
    }
}

ClickListener 구현 객체가 setter를 통해 외부 필드에 저장될 수 있도록 setter를 추가합니다.

public class Button {
    //정적 중첩 인터페이스
    public static interface ClickListener {
        //추상메소드
        void onClick();
    }

    //필드 
    private ClickListener clickListener;

    //메소드
    public void setClickListener(ClickListener clickListener) {
        this.clickListener = clickListener;
    }

    public void click() {
        this.clickListener.onClick();
    }
}

버튼을 클릭했을 때 실행할 메서드로 click()을 추가했습니다.

구현 세부 정보 onClick() 추상 메서드는 clickListener 인터페이스 필드를 사용하여 호출됩니다.

public class ButtonExample {
    public static void main(String() args) {
        // ok버튼 객체 생성
        Button btnOk = new Button();

        // Ok버튼 클릭 이벤트를 처리할 ClickListener 구현클래스(로컬 클래스)
        class OkListener implements Button.ClickListener {
            // 정적인터페이스라 바깥클래스.인터페이스
            @Override
            public void onClick() {
                System.out.println("Ok 버튼을 클릭했습니다.

"); } } // Ok버튼 객체에 ClickListener 구현객체 주입 btnOk.setClickListener(new OkListener()); // Ok버튼 클릭 btnOk.click(); //Ok 버튼을 클릭했습니다.

// ------------------------------------------ // Cancel 버튼 // ok버튼 객체 생성 Button btnCancel = new Button(); // Ok버튼 클릭 이벤트를 처리할 ClickListener 구현클래스(로컬 클래스) class CancelListener implements Button.ClickListener { // 정적인터페이스라 바깥클래스.인터페이스 @Override public void onClick() { System.out.println("Cancel 버튼을 클릭했습니다.

"); } } // Ok버튼 객체에 ClickListener 구현객체 주입 btnCancel.setClickListener(new CancelListener()); // Ok버튼 클릭 btnCancel.click(); //Cancel 버튼을 클릭했습니다.

} }

ClickListener 구현 클래스를 로컬 클래스로 개발하고 실행에 주입합니다.


자신만의 클래스를 생성하고 구현할 수 있습니다.

9.7 익명 객체

익명 개체는 이름이 없는 개체입니다.

클래스를 명시적으로 선언하지 않기 때문에 쉽게 객체를 생성할 수 있다는 장점이 있습니다.


익명 개체는 주로 필드 값, 로컬 변수 값 및 매개 변수 값으로 사용됩니다.


익명 개체는 클래스를 상속하거나 인터페이스를 구현해야만 만들 수 있습니다.


클래스를 상속받아 생성한 경우에는 익명의 하위객체, 인터페이스를 구현한 경우에는 익명의 구현객체라고 한다.

9.7.1 익명 하위 개체

익명 자식 개체는 부모 클래스에서 상속하여 생성됩니다.


이러한 방식으로 생성된 개체에는 상위 유형 필드, 지역 변수 및 매개 변수의 값을 할당할 수 있습니다.


익명 하위 개체가 부모 유형에 할당되기 때문에 부모 유형에서 선언된 멤버만 액세스할 수 있습니다.


중괄호 블록 안에는 일반적으로 부모 메서드를 재정의하는 코드가 있습니다.


새 부모 생성자() {자식 개체 내용}; / ; 첨부됨

public class Car {
    // 필드 Tire에 객체 대입
    private Tire tire1 = new Tire();

    // 필드에 익명 자식객체 대입
    private Tire tire2 = new Tire() {
        @Override
        public void roll() {
            System.out.println("익명 자식 Tire 객체 1이 굴러갑니다.

"); } }; //메소드(필드 이용) public void run1() { tire1.roll(); tire2.roll(); } //메소드(로컬 변수 이용) public void run2() { //로컬 변수에 익명자식객체 대입 Tire tire = new Tire() { @Override public void roll() { System.out.println("익명 자식객체 Tire 객체2가 굴러갑니다.

"); } }; tire.roll(); } //메소드()매개변수 이용 public void run3(Tire tire) { tire.roll(); } }

public class CarExample {
    public static void main(String() args) {
        // 객체생성
        Car car = new Car();

        // 필드에 익명 자식객체가 대입된 메소드 사용
        car.run1();
        //tire1.roll(); 일반 타이어가 굴러갑니다.

//tire2.roll(); 익명 자식 Tire 객체 1이 굴러갑니다.

// 로컬 변수에 익명자식객체가 대입된 메소드 사용 car.run2(); //tire.roll(); 익명 자식객체 Tire 객체2가 굴러갑니다.

// 매개변수에 익명자식객체가 대입되는 메소드 사용 car.run3(new Tire() { @Override public void roll() { System.out.println("익명 자식 Tire 객체 3이 굴러갑니다.

"); } }); //익명 자식 Tire 객체 3이 굴러갑니다.

} }

Tire 클래스에는 roll() 메서드가 있지만 익명의 하위 개체가 roll()을 재정의하여 실행(다형성)을 변경합니다.


익명 하위 객체가 부모 유형에 할당되면 부모 메서드 roll()이 호출될 때 재정의된 익명 하위 객체의 roll() 메서드가 실행됩니다.

9.7.2 익명 구현 개체

익명 구현 개체는 인터페이스를 구현하여 생성됩니다.


이러한 방식으로 생성된 객체는 인터페이스 유형 필드, 지역 변수 및 매개변수 값에 할당될 수 있습니다.


익명 구현 개체는 Android와 같은 UI 프로그램에서 이벤트를 처리하기 위한 개체로 자주 사용됩니다.


새로운 인터페이스() { 구현 };
중괄호 안의 필드와 메서드는 익명 구현 객체의 필수 멤버이며 해당 객체에서만 사용할 수 있습니다.


중괄호 블록 안에는 일반적으로 인터페이스의 추상 메서드를 재정의하는 코드가 있습니다.

public interface RemoteControl {
    //추상메소드
    void turnOn();
    void turnOff();
}

public class Home {
    // 필드에 익명 구현객체 대입
    private RemoteControl rc = new RemoteControl() {
        @Override
        public void turnOn() {
            System.out.println("TV를 켭니다.

"); } @Override public void turnOff() { System.out.println("TV를 끕니다.

"); } }; // 메소드 필드사용 public void use1() { rc.turnOn(); rc.turnOff(); } // 메소드(로컬변수 이용) public void use2() { // RemoteControl rc = new RemoteControl() {}; RemoteControl rc = new RemoteControl() { @Override public void turnOn() { System.out.println("에어컨을 켭니다.

"); } @Override public void turnOff() { System.out.println("에어컨을 끕니다.

"); } }; rc.turnOn(); rc.turnOff(); } //메소드 (매개변수 이용) public void use3(RemoteControl rc) { rc.turnOn(); rc.turnOff(); } }

public class HomeExample {
    public static void main(String() args) {
        // Home 객체 생성
        Home home = new Home();

        // 필드에 익명구현객체가 대입된 메소드 사용
        home.use1();

        // 로컬변수에 익명구현객체가 대입된 메소드 사용
        home.use2();

        // 매개변수에 익명구현객체를 대입해 메소드 사용
        // home.use3(new RemoteControl() {});
        home.use3(new RemoteControl() {

            @Override
            public void turnOn() {
                System.out.println("난방을 켭니다.

"); } @Override public void turnOff() { System.out.println("난방을 끕니다.

"); } }); } }

9.6 중첩된 인터페이스 예제를 수정하고 버튼 이벤트 처리 개체를 익명 구현 개체로 바꿉니다.

setter를 호출할 때 ClickListener의 익명 구현 개체를 매개 변수로 할당합니다.


구현 클래스를 명시적으로 만들지 않고 코드가 간결합니다.

public class ButtonExample {
    public static void main(String() args) {
        // ok버튼 객체 생성
        Button btnOk = new Button();

        // Ok버튼 객체에 ClickListener 익명구현객체 주입
        btnOk.setClickListener(new Button.ClickListener() {
            @Override
            public void onClick() {
                System.out.println("Ok 버튼을 클릭했습니다.

"); } }); // Ok버튼 클릭 btnOk.click(); // Ok 버튼을 클릭했습니다.

// ------------------------------------------ // Cancel 버튼 // ok버튼 객체 생성 Button btnCancel = new Button(); // Ok버튼 객체에 ClickListener 익명구현객체 주입 btnCancel.setClickListener(new Button.ClickListener() { @Override public void onClick() { System.out.println("Cancel 버튼을 클릭했습니다.

"); } }); // Ok버튼 클릭 btnCancel.click(); // Cancel 버튼을 클릭했습니다.

} }

2023.02.19 리뷰

중첩된 클래스 자체는 많이 사용되지 않는 것 같지만 익명 개체는 람다 식에서 많이 사용됩니다.


중첩 클래스의 특성에서 익명의 객체를 설명하듯 이 책이 잘 정리되어 있다는 느낌이 듭니다.


더 열심히 해야 할 것 같아요.