본문 바로가기

JAVA

익명 클래스와 익명객체(Anonymous Object)

익명 클래스

탄생 배경

추상 클래스와 인터페이스는 객체를 생성하려고 하면 에러가 발생하게 된다.

  • 메소드의 구현체가 존재하지 않기 때문이다.
    • 사실 default method는 자바 8 이후로 구현체를 인터페이스에 작성할 수 있게 됐다.
    • 하지만 통일성을 위해 이 규칙은 유지
    • default method를 구현해도 여전히 객체를 생성할 수 없다

 

 

익명 객체

익명 클래스는 이름 없는 일회성 클래스를 정의하며, 추상 클래스나 인터페이스를 상속(또는 구현) 하여 필요한 메서드를 즉시 재정의한 뒤 인스턴스를 생성한다.

 

abstract class A {
    abstract int method(int a, int b);
}

abstract class B {
    int method(int a, int b) {
        return a - b;
    }
}

interface I {
    int method(int a, int b);
}

public class Main {

    public static void main(String[] args) {
        A a = new A() {
            @Override
            int method(int a, int b) {
                return a + b;
            }
        };
        System.out.println(a.method(10, 2));


				//이미 작성된 메소드로 오버라이딩이 가능하다.
        B b = new B() {
            @Override
            int method(int a, int b) {
                return a / b;
            }
        };
        System.out.println(b.method(10, 2));


        I i = new I() {
            @Override
            public int method(int a, int b) {
                return a * b;
            }
        };
        System.out.println(i.method(10, 2));
    }
}

 

 

- 헷갈릴 수 있는 부분

익명 클래스를 사용하면 인터페이스나 추상 클래스를 객체로 만들 수 있구나!! (X)

추상클래스, 또는 인터페이스를 "상속 받는 객체"를 만들어주는 것이 핵심

A a = new A() { @Override ~~~} → A를 상속 받는 클래스를 인스턴스화 + 내부 클래스로 정의

 

 

 

코드를 통해 보자

abstract class A {
    abstract int method(int a, int b);
}

abstract class B {
    abstract int method(int a, int b);
}

public class Main {

    public static void main(String[] args) {
        A a1 = new A() {
            @Override
            int method(int a, int b) {
                return a - b;
            }
        };

        A a2 = new A() {
            @Override
            int method(int a, int b) {
                return a - b;
            }
        };

        B b = new B() {
            @Override
            int method(int a, int b) {
                return a * b;
            }
        };

        System.out.println("A.getClass() == "+ A.class);
        System.out.println("a1.getClass() == "+ a1.getClass());
        System.out.println("a1.getClass() == "+ a2.getClass());
        System.out.println("b.getClass() == "+ b.getClass());
    }
}

 

출력 결과

 

컴파일러가 자동으로 Main 클래스 안에 중첩(Nested)된 이름 없는 클래스로 생성

→ 부모 클래스의 하위로 가는게 아닌 Nested Class로 선언되는게 포인트

 

 

 

컴파일러 동작 예시

//실제로는 클래스가 다르지만 이해하기 편하게 이런식이라고 생각하면 됩니다 
public class Main {

    public static void main(String[] args) {
        class Main$1 extends A {
            @Override
            int method(int a, int b) {
                return a - b;
            }
        }

        class Main$2 extends A {
            @Override
            int method(int a, int b) {
                return a - b;
            }
        }

        class Main$3 extends B {
            @Override
            int method(int a, int b) {
                return a * b;
            }
        }
    }
}

 

 

사용예시)

  • 익명 클래스 사용 안할 시
import java.util.Comparator;
import java.util.PriorityQueue;

//Comparator 구현
class AscendingComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
}

public class NamedClassExample {
    public static void main(String[] args) {
        // 명시적 클래스의 인스턴스 생성 후 전달
        PriorityQueue<Integer> q = new PriorityQueue<>(new AscendingComparator());
    }
}
  • 익명 클래스 사용 시
PriorityQueue<Integer> q = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o
            }
        });

 

 

 

장단점

  • 장점 : 익명 클래스를 사용하면 지역 클래스의 선언과 생성을 한 번에 할 수 있다는 점에서 코드가 간결해진다.
  • 단점 : 복잡하거나 재사용이 필요한 경우에는 별도의 클래스를 정의
    • override 해야 할 메소드가 많거나 코드가 복잡하면 가독성이 떨어진다
    • 해당 override를 재사용 할 수 없기 때문에 재사용이 불가능