4. 클래스(심화)

사실 클래스에 대한 기본 개념은 공부했지만, 여기서는 중첩 클래스, 멤버 클래스, 로컬 클래스 등등의 좀 특이한(?) 애들에 대해서 간단하게 들여보고 바로 넘어갈 예정. 자료구조 커스터마이징 할 때 엄청 자주 쓰다 보니까 감각은 조금 살아있어서(?)

중첩 클래스는 멤버 클래스로컬 클래스로 나뉜다. 분류 기준은 선언 위치

1) 중첩 클래스

(1) 멤버 클래스

클래스 멤버로써 선언

  • 인스턴스 멤버 클래스 : A 객체 생성해야 B 객체 생성 가능
class A {
	class B {}
}

// A 클래스 외부에서 쓰려면...
A a = new A(); // 먼저 A 객체를 생성해야
A.B b = a.new B(); // B 객체를 생성 가능

바이트코드 파일(.class)은 A $ B .class

  • 정적 멤버 클래스 : A 객체 생성 안 해도 B 객체 생성 가능
class A {
	static class B {}
}

// A 클래스 외부에서 쓰려면...
A.B b = new A.B(); // A 객체 생성 없이 바로 가능

바이트코드 파일(.class)은 A $1 B .class

(2) 로컬 클래스

생성자나 메소드 내부에서 선언

class A {
	// 생성자
    A() {
    	class B {}
       
        B b = new B(); // 로컬 객체 생성을 하는 로직이 있어야 쓰니까...
    }

    // 메소드
    void method() {
    	class B {}
       
        B b = new B(); // 로컬 객체 생성을 하는 로직이 있어야 쓰니까...
    }
}

이런 식으로 작성하고 외부에서는

A a = new A(); // 생성자 로컬 클래스의 생성자 실행
a.method(); // 메소드 로컬 클래스의 생성자 실행

요런 식으로

2) 중첩 클래스의 바깥 클래스에 대한 접근

당근 가능

단, 정적 멤버 클래스는 바같 클래스의 인스턴스 필드, 인스턴스 메소드를 사용할 수 없다. 이유는 바깥 객체 없이도 정적 멤버 클래스가 사용 가능해야 되므로. 대신 바깥 클래스의 정적 필드와 정적 메소드는 사용 가능하다.

(애시당초 바깥 클래스의 객체가 생성되지 않아서 메소드나 필드를 써먹지 못하는데 중첩 클래스가 써먹는 게 어불성설이긴 하다)

만약 중첩 클래스와 바깥 클래스의 필드명이나 메소드명이 동일하면
바깥 클래스.this 키워드를 사용한다.

class A {
	Object field;
    
    void method() {}
    
    class B {
    	Object field;
        
        void method() {
        	// 바깥 클래스의 필드에 접근
        	System.out.println(A.this.filed);
            
            // 바깥 클래스의 메소드에 접근
            A.this.method();
        }
    }
}

3) 중첩 인터페이스

클래스 내부에 선언된 인터페이스를 뜻함
중첩 클래스처럼 인스턴스 중첩 인터페이스, 정적 중첩 인터페이스 전부 가능

중첩 인터페이스는 보통 다형성 구현에서도 의의를 가진다.

// Button 클래스
// 내부에 정적 중첩 인터페이스 선언
public class Button {
    // static nested interface
    public static interface ClickListener {
        // abstract method
        void onClick();
    }

    // field
    private ClickListener clickListener;

    // method (setter) for polymorphism
    public void setClickListener(ClickListener clickListener) {
        this.clickListener = clickListener;
    }

    // method
    public void click() {
        this.clickListener.onClick();
    }
}
// ButtonExample 클래스
// 다형성 구현
public class ButtonExample {
    public static void main(String[] args) {
        // 객체 생성
        Button button = new Button();

        class OkListener implements Button.ClickListener {
            @Override
            public void onClick() {
                System.out.println("click OK button");
            }
        } // 먼저 정적 중첩 인터페이스를 구현하는 OK 리스너 클래스를 선언한 후...

        // 버튼 객체에 인터페이스 구현 객체 주입
        button.setClickListener(new OkListener());
        // 매개변수 타입이 인터페이스인데 왜 실현 객체가 들어갈 수 있냐면 다형성 때문
        // ClickListener clickListener = new OkListener();

        button.click(); // 그리고 메소드 실행~

        /* 다형성을 추가로 활용하자면... */

        // 이번엔 또 다른 인터페이스 구현 클래스 선언
        class CancelListener implements Button.ClickListener {
            @Override
            public void onClick() {
                System.out.println("click Cancel Button");
            }
        }

        button.setClickListener(new CancelListener());

        button.click();
    }
}

이런 식으로 다형성 구현 가능!

4) 익명 객체

멀리 갈 것 없이, Thread 생성할 때 오버라이딩 하면서 run() 메소드 구현하던 과정 생각하기. 참고로 내가 계속 구현하던 것은 익명 구현 객체. 클래스를 상속한 익명 객체는 익명 상속 객체.
(살짝 느낌이 자바스크립트의 화살표 익명 함수랑 비슷한데...?)

당시 공식처럼 외웠던 것은, new 클래스() { // .. 여기서부터 구현 시작 } 이런 내용이었음.

  • 익명 자식 객체
new 부모생성자(매개값, ...) {
	// 필드
    // 메소드
}
  • 익명 구현 객체
new 인터페이스() {
	// 필드
    // 메소드
}

여담으로, 익명 객체를 실현할 때, 익명 자식 객체에서 extends가 불필요하고 익명 구현 객체에서 implements가 불필요하다.

profile
scientia est potentia / 벨로그 이사 예정...

0개의 댓글