이것이 자바다 7일차 - Chapter9 중첩 선언과 익명 객체

Seo-Faper·2023년 1월 17일
0

이것이 자바다

목록 보기
9/20

중첩 클래스란

중첩 클래스란 클래스 내부에 선언한 클래스를 말한다.
중첩 클래스를 사용하면 클래스의 멤버를 쉽게 사용할 수 있고 외부에는 중첩 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다.

중첩 클래스는 선언하는 위치에 따라 두 가지로 분류된다. 클래스의 멤버러소 선언되는 중첩 클래스를 멤버 클래스라고 하고 메소드 내부에서 선언되는 중첩 클래스를 로컬 클래스라고 한다.

선언 위치객체 생성 조건
class A { class B {...} }A 객체를 생성해야함 B 객체 생성 가능
class A { static class B {...} }A 객체를 생성하지 않아도 B 객체를 생성할 수 있음
class A { void method() { class B {...} } }method가 실행할 때만 B 객체를 생성할 수 있음

중첩 클래스도 하나의 클래스이기 때문에 컴파일 시 바이트코드 파일(*.class)이 별도로 생성된다. 멤버 클래스일 경우 바이트 코드 파일은 다음과 같이 결정된다.

A $ B . class
|   |
|   ㄴ 멤버 클래스
ㄴ 바깥 클래스 

로컬 클래스일 경우는 다음과 같이 $1이 포함된 바이트코드 파일이 생성된다.

A $1 B . class

인스턴스 멤버 클래스

인스턴스 멤버 클래스는 다음과 같이 A 클래스의 멤버로 선언된 B 클래스를 말한다.

[public] class A {
	[public | private] class B { ... } //<- 인스턴스 멤버 클래스 
}

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

구분접근 범위
public class B{ ... }다른 패키지에서 B 클래스를 사용할 수 있다.
class B { ... }같은 패키지에서만 B 클래스를 사용할 수 있다.
private class B { ... }A 클래스내부에서만 B 클래스를 사용할 수 있다.

인스턴스 멤버 클래스 B는 주로 A 클래스 내부에 사용되므로 private로 선언하는게 일반적이다. B 객체는 A 클래스 내부 어디에서나 생성할 수 없고, 인스턴스 필드값, 생성자, 인스턴스 메소드에서 생성할 수 있다. A 객체가 있어야 B 객체도 생성될 수 있기 때문이다.

package ch09.sec02.exam01;

public class A {
    
    //인스턴스 멤버 클래스
    class B{}
    
    // 인스턴스 필드 값으로 B 객체 대입
    B field = new B();

    //생성자
    A(){
        B b = new B();
    }
    
    // 인스턴스 메소드
    void method(){
        B b = new B();
    }
}
package ch09.sec02.exam01;

public class AExample {
    public static void main(String[] args) {
        A a = new A();

        A.B b = a.new B();
    }
}

정적 멤버 클래스

[public] class A {
	[public | private] static class B { ... } //<- 정적 멤버 클래스 
}

이렇게 내부 클래스에 static 이 붙어있으면 정적 맴버로 선언된다.

구분접근 범위
public static class B{ ... }다른 패키지에서 B 클래스를 사용할 수 있다.
static class B { ... }같은 패키지에서만 B 클래스를 사용할 수 있다.
private static class B { ... }A 클래스내부에서만 B 클래스를 사용할 수 있다.

정적 멤버 클래스는 주로 클래스 외부에서 함께 사용되기 때문에 주로 default 또는 public 접근 제한자를 주로 가진다.
B 객체는 A 캘래스 내부 어디든 생성 할 수 있기 때문이다.

로컬 클래스

생성자 또는 메소드 내부에서 선언된 클래스를 로컬 클래스라고 한다.

[public] class A{
	//생성자
    public A() {
    	class B { ... } // 로컬 클래스
    }
    
    //메소드
    public void method(){
    	class B {...} // 로컬 클래스
    }
}

로컬 변수를 로컬 클래스에서 사용할 경우 로컬 변수는 final의 특성을 가지게되어 읽는 것만 가능하다.

바깥 멤버 클래스

바깥 클래스의 멤버 접근

중첩 클래스는 바깥 클래스와 긴밀한 관계를 맺으면서 바깥 클래스의 멤버(필드, 메소드)에 접근할 수 있다. 하지만 중첩 클래스가 어떻게 선언되었느냐에 따라 접근 제한이 있을 수 있다.

package ch09.sec05.exam01;

public class A {
    int field1;
    void method1(){}
    static int field2;
    static void method2(){}

    class B{
        void method(){
            field1 = 10;
            method1();

            field2 = 10;
            method2();
        }
    }
    static class C{
        void method(){
            //field1 = 10; 정적 멤버에서는 바깥 객체가 없어도 사용가능해야 하므로 사용 불가
            //method1();

            //static으로 선언된 필드와 메소드는 가능
            field2 = 10;
            method2();;
        }
    }
}

여기 보면 static으로 선언된 내부 클래스는 바깥 클래스의 필드와 메소드를 쓸 수 없는 반면 같은 static으로 선언된 바깥 클래스의 필드와 메소드는 사용 가능하다.
당연하다. 컴파일 과정에서 static으로 선언된 애들이 무조건 먼저 메소드 영역에 박제 당할 테니까. 그래서 무분별한 static 선언은 프로그램 메모리를 많이 잡아 먹게 된다. 어디에서도 호출 가능하니 관리도 힘들어지고.

바깥 클래스의 객체 접근

바깥클래스이름.this // 바깥 객체

중첩 클래스에서 this는 해당 줒첩 클래스의 객체를 말한다. 만약 중첩 클래스 내부에서 바깥 클래스의객체를 얻으려면 바깥 클래스 이름에 this를 붙여주면 된다.

중첩 인터페이스

중첩 인터페이스란 클래스의 멤버로 선언된 인터페이스를 말한다. 인터페이스를 클래스 내부에 선언하는 이유는 해당 클래스와 긴밀한 관계를 맺는 구현 객체를 만들기 위해서다.

class A {
	[public | private] [static] interface B{
    	//상수 필드
        //추상 메소드
        //디폴트 메소드
        //정적 메소드
    }
}

외부의 접근을 막지 않으려면 public, A 클래스 내부에서만 사용하려면 private를 쓰고 접근 제한자를 붙이지 않을 시 같은 패키지 안에서만 접근이 가능하다. 그리고 A 객체 없이 B인터페이스를 사용할 수 있도록 하기 위해 static을 추가할 수 있다.

중첩 인터페이스는 안드로이드와 같은 UI 프로그램에서 이벤트를 처리할 목적으로 많이 활용된다.

package ch09.sec06.exam01;

public class Button {
    public static interface ClickListener{

        void onClick();
    }
}

onClick() 메소드는 버튼이 클릭되었을 때 호출될 메소드이다.
Button 클래스에 ClickListener 타입의 필드와 Setter를 추가해서 외부에서 Setter를 통해 ClickListener구현 객체를 필드에 저장할 수 있도록 하자.

package ch09.sec06.exam03;

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

Button이 클릭되었을 때 실행할 메소드를 click()이라고 하고 선언한다. 실행 내용은 ClickListener 인터페이스 필드를 이용해서 onClick() 추상 메소드를 호출한다.

package ch09.sec06.exam03;

public class ButtonExample {
    public static void main(String[] args) {
        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());

        btnOk.click();
        //---------------------------

        Button btnCancel = new Button();
        class CancelListener implements Button.ClickListener{

            @Override
            public void onClick() {
                System.out.println("Cancel 버튼을 클릭했습니다.");
            }
        }
        btnCancel.setClickListener(new CancelListener());
        btnCancel.click();
    }
}

이렇게 하나의 Button 클래스에서 어떤 구현 객체를 주입하는지에 따라 인터페이스를 이용한 다형성을 구현할 수 있다.

익명 객체

말 그대로 이름이 없는 객체를 말한다. 명시적으로 클래스를 선언하지 않기 때문에 쉽게 객체를 생성할 수 있다는 장점이 있다. 익명 객체는 필드값, 로컬 변수값, 매개변수값으로 주로 사용된다. 익명 객체는 클래스를 상속하거나 인터페이스를 구현해야만 생성할 수 있다. 클래스를 상속해서 만들경우 익명 자식 객체라고 하고, 인터페이스를 구현해서 만들 경우 익명 구현 객체라고 한다.

익명 자식 객체

new 부모생성자(매개값, ...)
{
 ...
}

부모 클래스를 상속받아 다음과 같이 생성된다. 이렇게 생성된 객체는 부모 타임의 필드, 로컬 변수, 매개변수의 값으로 대입할 수 있다.

package ch09.sec07.exam01;

public class Tire {
    public void roll(){
        System.out.println("일반 타이어가 굴러갑니다.");
    }
}
package ch09.sec07.exam01;

public class Car {
    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();
    }
}
package ch09.sec07.exam01;

public class CarExample {
    public static void main(String[] args) {
        Car car = new Car();

        car.run1();

        car.run2();

        car.run3(new Tire(){
            @Override
            public void roll() {
                System.out.println("익명 자식 Tire 객체 3이 굴러갑니다.");
            }
        });
    }
}

이렇게 필드에 익명 자식 객체를 대입할 수도 있고 로컬 변수에 대입 할수도 있고 익명 자식 객체가 대입된 매개변수를 사용할 수도 있다.

익명 구현 객체

new 인터페이스(){
	//필드
    //메소드
}
package ch09.sec07.exam02;

public interface RemoteControl {
    void turnOn();
    void turnOff();
}
package ch09.sec07.exam02;

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() {
            @Override
            public void turnOn() {
                System.out.println("에어컨을 킵니다.");
            }

            @Override
            public void turnOff() {
                System.out.println("에어컨을 끕니다.");
            }
        };
    }
    public void use3(RemoteControl rc){
        rc.turnOn();
        rc.turnOff();
    }
}
package ch09.sec07.exam02;

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

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

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

        // 익명 구현 객체가 대입된 매개변수 사용
        home.use3(new RemoteControl() {
            @Override
            public void turnOn() {
                System.out.println("난방을 킵니다.");
            }

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

import ch09.sec06.exam03.Button;

public class ButtonExample {
    public static void main(String[] args) {
        Button btnOk = new Button();
        btnOk.setClickListener(new Button.ClickListener() {
            @Override
            public void onClick() {
                System.out.println("Ok 버튼을 클릭했습니다.");
            }
        });

        btnOk.click();

        Button btnCancel = new Button();
        btnCancel.setClickListener(new Button.ClickListener() {
            @Override
            public void onClick() {
                System.out.println("Cancel 버튼을 클릭했습니다.");
            }
        });

        btnCancel.click();
    }
}

일전에 만들었던 중첩 인터페이스 예제를 수정한 것이다.
버튼 이벤트 처리 객체를 익명 구현 객체로 대채한 것인데, Setter를 호출할 때 매개값으로 ClickListener익명 구현 객체를 대입했다.
명시적인 구현 클래스를 생성하지 않기 때문에 코드가 간결해 진 것을 볼 수 있다.

연습문제

4번, 바깥 클래스의 정적 필드만 쓸 수 있습니다. 같은 static 집안끼리만 쓸 수 있죠.

3번, 로컬 클래스는 메소드 내부에 선언된 클래스인데 static으로 선언하면 메소드가 호출되지도 않았는데 쓸 수 있게 되겠죠..


3번, 익명 객체는 명시적으로 클래스를 선언하지 않기 때문에 생성자를 선언할 수 없습니다.


myCar.new Tire();
new Car.Engine(); 

Car 객체의 내부 클래스기 때문에 myCar.new 로 생성하는 반면
static으로 선언되어 있으면 바로 접근 할 수 있지요.

new( Action(){
	@Override
    public void work(){
		System.out.println("복사를 합니다.);
      }
});

코드가 간결해 지네요.


필드에서 하는 법

Vehicle field = new Vehicle() {
	@Override
    public void run(){
    	System.out.println("자전거가 달립니다.");
    }
}

로컬 변수의 초기값으로 하는 법

Vehicle localVar = new Vehicle() {
	@Override
    public void run(){
    	System.out.println("승용차가 달립니다.");
    }
}

메소드의 매개값으로 대입하는법

anony.method2( new Vehicle(){
	@Override
    public void run(){
    	System.out.println("트럭이 달립니다.");
    }
});

로컬 변수인 nickName을 로컬 클래스인 Chat에서 사용하게 될 경우 nickName은 final의 특성을 갖게 되기에 nickName에 null이 들어간 이상 변경할 수 없게되죠. 그래서 String nickName = chatId; 이렇게 처음에 대입해야 합니다.

profile
gotta go fast

0개의 댓글