2023.02.19 Java 복습

9. 중첩 선언 및 익명 개체

9.1 중첩 클래스

객체 지향 프로그램에서 클래스는 긴밀한 관계를 통해 서로 상호 작용합니다.
하나의 클래스가 여러 클래스와 관계가 있는 경우에는 독립적으로 선언하는 것이 좋지만 특정 클래스만 관계가 있는 경우 중첩된 클래스로 선언하는 것이 유지 관리에 유용한 경우가 많습니다.
중첩 클래스는 클래스 내에서 선언되는 클래스입니다. 중첩 클래스를 사용하면 클래스의 멤버를 쉽게 사용할 수 있고 중첩 클래스 관계를 외부에서 숨겨 코드 복잡성을 줄일 수 있다는 장점이 있습니다.
중첩 클래스가 선언된 위치에 따라 두 가지 유형으로 나뉩니다.
클래스 멤버로 선언된 중첩 클래스를 멤버 클래스라고 하고 메서드 내부에 선언된 중첩 클래스를 로컬 클래스라고 합니다.

중첩된 클래스도 클래스이므로 컴파일 시 별도의 바이트코드 파일이 생성됩니다.
1. 회원등급의 경우
바이트코드 파일명은 A(outer class)$B(member class).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입니다.

접근 한정자에 따른 인스턴스 멤버 클래스의 접근 범위는 다음과 같다.
다른 패키지에서 사용 가능한 공개 정적
기본 정적과 같은 패키지에서만
private static 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();
    }
}

로컬 클래스에서 로컬 변수를 사용하는 경우 로컬 변수는 end 속성을 가지므로 값을 읽을 수만 있고 변경할 수는 없습니다.
이는 로컬 클래스 내에서 값이 변경되는 것을 방지하기 때문입니다.
Java 8부터는 더 이상 명시적으로 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 외부 클래스 구성원에 대한 액세스 제한

정적 멤버 클래스 내에서 외부 클래스의 필드 및 메서드 사용에 대한 제한이 있습니다.
인스턴스 멤버 클래스 -> 외부 클래스의 모든 필드 및 메서드/
-> B객체를 만들기 위해서는 A객체를 만들어야 하므로 아무거나 사용할 수 있을 것 같습니다.
정적 멤버 클래스 -> 외부 클래스의 정적 필드 및 정적 메서드
-> 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 객체 없이도 사용할 수 있으므로 외부에서 접근이 가능합니다.
onClick 메서드는 버튼을 클릭할 때 호출되는 메서드입니다.

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

    //필드 
    private ClickListener clickListener;

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

Setter를 통해 외부에서 ClickListener 구현 개체를 필드에 저장할 수 있도록 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()을 추가했습니다. 실행 세부 정보는 clickListener 인터페이스 필드를 사용하여 추상 onClick() 메서드를 호출합니다.

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년 2월 19일 검토

중첩된 클래스 자체는 많이 사용되지 않는 것 같지만 익명 개체는 람다 식에서 자주 사용됩니다.
나는 책이 매우 잘 구성되어 있다는 것을 알았습니다. 중첩 클래스의 속성에서 익명 객체를 선언하는 방법을 배운 것 같습니다.
더 부지런해야 할 것 같아요.