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

/* 일반적인 케이스 */
// 상속
class 클래스이름1 extends 부모클래스 {...}
부모클래스 변수 = new 클래스이름1();

// 구현
class 클래스이름2 implements 인터페이스 {...}
인터페이스 변수 = new 클래스이름2();


/* 익명 객체 */
// 상속
부모클래스 변수 = new 부모클래스() {...};

// 구현
인터페이스 변수 = new 인터페이스() {...};


익명 객체의 경우, 부모 클래스 변수는 이름이 없는 자식 객체를 참조하고 인터페이스 변수는 이름이 없는 구현 객체를 참조합니다.

익명 자식 객체 생성

부모 타입의 필드 또는 변수를 선언하고 자식 객체를 초기값으로 대입하는 경우를 생각해 봅니다. 우선 부모 클래스를 상속해서 자식 클래스를 선언합니다. 그리고 new 연산자를 이용해서 자식 객체를 생성한 후 부모 타입의 필드 또는 변수에 대입하는 것이 일반적입니다.

class Child extends Parent { } // 자식 클래스 선언

public class A {
    Parent field = new Child(); // 필드에 자식 객체 대입
    
    void m() {
        Parent localField = new Child(); // 로컬 변수에 자식 객체 대입
    }
}


자식 클래스를 명시적으로 선언하는 이유는 어디에서나 이미 선언된 자식 클래스로 간단히 객체를 생성해서 사용할 수 있기 때문입니다. 이러한 이점때문에 재사용성이 높습니다.


그러나 자식 클래스가 재사용되지 않고 특정 위치에서만 사용할 경우라면 자식 클래스를 명시적으로 선언하는 것은 번거로운 작업이 됩니다. 이 경우에 익명 자식 객체를 생성해서 사용하는 것은 좋은 방법이 됩니다.

부모클래스 [필드:변수] = new 부모클래스(매개값, ...) {
    // 필드
    // 메소드
}; 
// 하나의 실행문이므로 세미콜론 필수


부모 클래스(매개값, ...) {...} 은 부모 클래스를 상속해서 중괄호 {} 와 같이 자식 클래스를 선언합니다. 그리고 new 연산자는 이렇게 선언된 자식 클래스를 객체로 생성합니다.

부모 클래스(매개값, ...) 은 부모 생성자를 호출하는 코드로 매개값은 부모 생성자의 매개 변수입니다. 중괄호 내부에는 필드나 메소드를 선언하거나 부모 클래스의 메소드를 재정의하는 내용을 작성합니다. (일반적으로 재정의 메소드가 많이 나옴) 일반 클래스와의 차이점으로 생성자를 선언할 수 없습니다.


다음은 필드를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입하는 예시입니다.

public class A {
    // A 클래스의 필드 선언
    Parent field = new Parent() {
        int childField;
        void childMethod() { }
        
        // Parent의 메소드 재정의
        @Override
        void parentMethod() { }
    };
}


다음은 메소드 내에서 로컬 변수를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입하는 예시입니다.

public class A {
    void method() {
        // 로컬 변수 선언
        Parent field = new Parent() {
            int childField;
            void childMethod() { }

            // Parent의 메소드 재정의
            @Override
            void parentMethod() { }
        };
    }
}


다음은 메소드의 매개 변수가 부모 타입일 경우 메소드를 호출하는 코드에서 익명 자식 객체를 생성해서 매개값으로 대입하는 예시입니다.

public class A {
    void method1(Parent parent) { }
    void method2() {
        // method1() 메소드 호출
        method1(
                // method1()의 매개값으로 익명 자식 객체를 대입
                new Parent() {
                    int childField;
                    void childMethod() { }

                    // Parent의 메소드 재정의
                    @Override
                    void parentMethod() { }
                }
        );
    }
}


익명 자식 객체에 새롭게 정의된 필드와 메소드는 익명 자식 객체 내부에서만 사용되고 외부에서는 접근할 수 없습니다. (익명 자식 객체는 변수에 대입되므로 부모 타입에 선언된 것만 사용할 수 있기 때문에)

class Parent {
    void parentMethod() { }
}


public class A {
    Parent field = new Parent() {
        int childField;
        void childMethod() { }

        // Parent의 메소드 재정의
        @Override
        void parentMethod() { 
            childField = 3;
            childMethod();
        }
    };
    
    void method() {
        field.childField = 333; // X
        field.childMethod(); // X
        field.parentMethod(); // O
    }
}

익명 구현 객체 생성

인터페이스 타입의 필드 또는 변수를 선언하고 구현 객체를 초기값으로 대입한다고 할 때, 먼저 구현 클래스를 선업합니다. 다음 new 연산자를 사용해 구현 객체를 생성한 후 인터페이스 타입의 필드 또는 로컬 변수에 대입하는 것이 일반적입니다.

class TV implements RemoteControl { }


public class A {
    RemoteControl field = new TV(); // 필드에 구현 객체를 대입
    
    void method() {
        RemoteControl v = new TV(); // 로컬 변수에 구현 객체를 대입
    }
}


구현 클래스를 명시적으로 선언하는 이유는 어디에서나 이미 선언된 구현 클래스로 간단히 객체를 생성해서 사용할 수 있기 때문입니다. 이러한 이점때문에 재사용성이 높습니다.


그러나 구현 클래스가 재사용되지 않고 특정 위치에서만 사용할 경우라면 구현 클래스를 명시적으로 선언하는 것은 번거로운 작업이 됩니다. 이 경우에 익명 구현 객체를 생성해서 사용하는 것은 좋은 방법이 됩니다.

인터페이스 [필드:변수] = new 인터페이스 () {
    // 인터페이스에 선언된 추상 메소드의 실체 메소드 선언
    // 필드
    // 메소드
}


인터페이스() {...} 는 인터페이스를 구현해서 중괄호 {} 와 같이 클래스를 선언하라는 뜻입니다. 그리고 new 연산자는 이렇게 선언된 구현 클래스를 객체로 생성합니다. 중괄호 {}에는 인터페이스에 선언된 모든 추상 메소드의 실체 메소드를 작성(재정의)해야 합니다. 그렇지 않으면 컴파일 에러가 발생합니다.

  • 추가로 필드와 메소드를 선언할 수 있지만 실체 메소드에서만 사용 가능하고 외부에서는 사용할 수 없음


다음은 필드를 선언할 때 초기값으로 익명 구현 객체를 생성해서 대입하는 예시입니다.

public class A {
    // 클래스 A의 필드 선언
    RemoteControl field = new RemoteControl() {
        // RemoteControl 인터페이스의 추상 메소드에 대한 실체 메소드
        @Override
        void turnOn() { }
    }; 
}


다음은 메소드 내에서 로컬 변수를 선언할 때 초기값으로 익명 구현 객체를 생성해서 대입하는 예시입니다.

public class A {
    void method() {
        // 로컬 변수 선언
        RemoteControl field = new RemoteControl() {
            // RemoteControl 인터페이스의 추상 메소드에 대한 실체 메소드
            @Override
            void turnOn() { }
        };
    }
}


다음은 메소드의 매개 변수가 인터페이스 타입일 경우 메소드를 호출하는 코드에서 익명 구현 객체를 생성해서 매개값으로 대입하는 예시입니다.

public class A {
    void method1(RemoteControl rc) { }
    
    void method2() {
        // method1() 메소드 호출
        method1(
                // method1()의 매개값으로 익명 구현 객체를 대입
                new RemoteControl() {
                    // RemoteControl 인터페이스의 추상 메소드에 대한 실체 메소드
                    @Override
                    void turnOn() {}
                }
        );
    }
}

익명 객체의 로컬 변수 사용

중첩 클래스에서와 동일하게 로컬 변수에서 익명 객체를 사용하면 매개 변수나 로컬 변수를 final로 선언하도록 요구합니다.

public interface Calculator () {
    int sum();
}

public class A {
    public void m (final int a, int b) {
        final int x = 0;
        int y = 0;
        int field = 10;

        a = 10; // final 변수를 수정할 수 없음
        b = 20; // 자바 8 이후 내부적으로 final로 인식

        x = 40; // final 변수를 수정할 수 없음
        y = 110; // 자바 8 이후 내부적으로 final로 인식

        Calculator calc = new Calculator() {
            @Override
            public int sum() {
                int result = field + a + b + x + y;
                return result;
            }
        };
    }
}


'언어 > Java' 카테고리의 다른 글

[Java] Map 컬렉션  (0) 2021.01.30
[Java] Set 컬렉션  (0) 2021.01.30
[JAVA] 익명 객체  (0) 2021.01.16
[Java] 중첩 클래스와 중첩 인터페이스  (0) 2021.01.16
[Java] 인터페이스  (0) 2021.01.10
[Java] 추상클래스  (0) 2021.01.09