Anonymous Class(익명 클래스)

2025. 5. 28. 16:09JAVA/JAVA 기초 문법

익명 클래스는 이름이 없는 일회용 클래스다.
클래스를 선언하고 인스턴스를 생성하는 코드가 한곳에 존재하는 구조로, 주로 인터 페이스나 추상 클래스를 상속받아 짧게 구현할 때 사용한다.
이름이 없는 것을 제외하면 지역 클래스와 비슷하다. 지역 클래스를 한번만 사용해야 할 경우 익명 클래스를 사용하면 된다.

지역 클래스가 클래스 선언인 반면, 익명 클래스는 expression이다. 이는 다른 expression내에서 클래스를 정의한다는 것을 의미한다.

익명클래스를 사용하는 경우는

  • 특정 클래스나 인터페이스를 한 번만 구현할 필요가 있을 때
  • 주로 이벤트 처리, 콜백, 간단한 전략 구현 등에 사용됨

📘 익명 클래스의 문법

앞서 언급했듯이 익명 클래스는 expression이다. 익명 클래스 extension의 문법은 코드 블록에 클래스 정의가 포함되어 있다는 점을 제외하면 생성자의 호출과 유사하다.

 

🔹 익명 클래스 표현식 (Anonymous Class Expression) 구성 요소

타입 이름 = new 타입() {
    // 익명 클래스의 본문
    // 메서드 오버라이딩 또는 구현
};

 

1️⃣ new 연산자

익명 클래스는 객체를 생성하면서 동시에 정의하기 때문에 new 키워드로 시작한다

 

2️⃣ 구현할 인터페이스 또는 상속할 클래스 이름
익명 클래스는 반드시 기존 인터페이스를 구현(implement)하거나 클래스를 확장(extend)해야 한다.

new 인터페이스이름() { ... }
new 클래스이름() { ... }

 

3️⃣ 괄호 ( ) -> 생성자 호출

일반 클래스 인스턴스 생성처럼 괄호로 생성자를 호출한다.
인터페이스 구현의 경우, 생성자가 없기 때문에 빈 괄호 ( ) 를 사용한다.

new HelloWorld() { ... }   // 인터페이스: 빈 괄호
new SomeClass("파라미터") { ... }  // 클래스: 생성자에 인자 전달 가능

 

4️⃣ 클래스 본문 { ... }

익명 클래스의 메서드 구현이 들어가는 영역으로, 이 안에서는 메서드 선언/오버라이드, 필드선언이 가능하다.
하지만, 메서드 외부에 일반적인 statement(문장)는 올 수 없다.

 

📃 예제

HelloWorld hello = new HelloWorld() { // new HelloWorld() -> 인터페이스 구현
    @Override
    public void greet() {    // great() 메서드를 익명 클래스 안에서 구현
        System.out.println("안녕하세요!");
    }
};

 

⚙️ 익명클래스 구현

1. 인터페이스 구현 (Implements Interface)

가장 일반적인 사용 형태로, 인터페이스를 익명 클래스로 즉석에서 구현한다.

public interface Animal {
	void sound();
	void move();	
}
public class Main {
    public static void main(String... args) {
        // new Animal() {...} : 익명 클래스 객체를 만드는 문법
        // 인터페이스 이름을 클래스 컨스트럭터 호출식처럼 사용하고 있음.
        // : 굳이 익명클래스 사용 이유?
        // -> 특정 메서드를 위한 구현체를 만들수 있음
        Animal animal = new Animal() {

        @Override
        public void sound() {
            System.out.println("야옹");				
        }
        @Override
        public void move() {
            System.out.println("살금살금");				
        }
    };	
    animal.sound();
    animal.move();
    }
}

출력시

야옹
살금살금

 

왜 인터페이스 구현이 많으 쓰일까

✅ 인터페이스는 인스턴스화 불가 반드시 구현체가 필요하므로 익명 클래스가 간편한 대안
✅ 코드가 짧고 간단함 사용 지접에 바로 구현할 수 있어 로컬성 유지
✅ 이벤트 핸들링에 최적 버튼 클릭, 마우스 이벤트 등에서 자주 사용됨

 

 

2. 추상 클래스 상속(Extends Abstract Class)

익명 클래스는 추상 클래스(Abstract Class)를 즉석에서 상속(extends)하여, 그 추상 메서드를 구현할 수 있다.
이 방식은 추상 클래스의 일회성 구현이 필요할 때, 상속으로 기본 기능을 재사용하면서 특정 메서드만 오버라이드할 때 유용하다

📃 예제 1. 기본 추상 클래스 상속

public class Animal {
    // Animal 클래스의 내부 추상 클래스 Dog 정의
    abstract class Dog {
        abstract void sound();		
        abstract void move();
    }
}
public class Main {
    public static void main(String... args) {
        // 1. 외부 클래스 Animal의 인스턴스를 먼저 생성해야 내부 클래스 접근 가능
        Animal animal = new Animal();
 
        // 2. animal 인스턴스를 통해 내부 추상 클래스 Dog를 익명 클래스로 상속 및 구현
        Animal.Dog dog = animal.new Dog() {

            @Override
            void sound() {
                System.out.println("멍멍");				
            }

            @Override
            void move() {
                System.out.println("살금살금");					
            }			
        };
        dog.sound();
        dog.move();
    }
}

출력시

멍멍
살금살금

 

📃 예제 2. 생성자 인자가 있는 추상 클래스

public class Animal {
    // 내부 추상 클래스 Dog 정의
    // Animal 인스턴스를 통해서만 생성 가능(비정적 내부 클래스)
    abstract class Dog {
        private String name;

        // 생성자를 통해 이름을 필드에 저장
        public Dog(String name) {
            this.name = name;
        }

        abstract void sound();

        public void introduce() {
            System.out.println("저는 " + name + " 입니다!!");
        }
    }	
}
public class Main {
    public static void main(String... args) {
        // 1. Animal 외부 클래스의 인스턴스 생성
        // 내부 클래스 Dog에 접근하려면 반드시 필요함!!
        Animal animal = new Animal();

        // 2. 익명 클래스를 통해 Animal.Dog를 상속 + "초코"이름 전달
        // 추상 메서드 sound()는 반드시 구현
        Animal.Dog dog = animal.new Dog("초코") {

            @Override
            void sound() {
                System.out.println("멍멍");				
            }			
        };		
        dog.sound();
        dog.introduce();	
    }
}

출력시

멍멍
저는 초코 입니다!!
  • animal.new Dog("초코") { ... }
    → 비정적 내부 추상 클래스를 익명 클래스로 상속하면서 생성자 호출까지 수행하는 문법
  • abstract void sound();
    → 반드시 익명 클래스 안에서 구현해야하는 인스턴스 생성 가능
  • introduce()는 일반 메서드 → 그대로 사용 가능, 재정의는 선택

🗂️ 추상 클래스 vs 인터페이스

  인터페이스 구현 추상 클래스 상속
키워드 implements extends
생성자 사용 불가능(인터페이스엔 생성자 없음) 가능
필드 포함 불가능(인터페이스는 상수만 가짐) 가능(상속됨)
사용 상황 동작만 정의하고 싶을 때 로직/상태를 재사용하고 싶을 때

 

⚙️ 익명 클래스의 외부 지역 변수 접근 규칙 

로컬 클래스와 마찬가지로 익명 클래스도 변수를 캡쳐할 수 있다. 그들은 둘러싸는 범위의 지역 변수에 대한 동일한 엑세스 권한을 갖는다.

단 조건이 있다!!

익명 클래스는 외부 메서드의 지역 변수에 접근할 수 있지만,
그 변수는 반드시 "final" 또는 "사실상(effectively) final" 이어야 한다.

 

🤔 왜 final 또는 사실상 final 이어야 할까?

  • 익명 클래스는 실행 시점에 별도의 객체로 존재하므로, 외부 메서드의 지역 변수 값이 변하면 동기화가 안 되기 때문
  • 그래서 컴파일러는 "변하지 않는 값"만 허용한다!!

💥 final 또는 사실상 final이 아닐경우!!

public class Demo {
    public void test() {
        int number = 10;
        number = 20; // ❌ 변수 값이 변경됨 → 더 이상 effectively final 아님

        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("number = " + number); // ❌ 컴파일 에러
            }
        };
        r.run();
    }

    public static void main(String[] args) {
        new Demo().test();
    }
}

컴파일 에러가 발생한다!!

 

그래서 익명 클래스가 외부 메서드의 지역 변수에 접근하기 위해서는 final 또는 사실상 final 형태로 만들어야한다

public class Demo {
    public void test() {
        final int age = 20; // ✅ final
        int number = 10; // ✅ 또는 effectively final
       
        // 익명 클래스 선언
        Runnable r = new Runnable() {
            @Override
            public void run() {
                // age, number 변수에 접근 가능
                System.out.println("age = " + age);
                System.out.println("number = " + number);
            }
        };

        r.run(); // 출력: age = 20, number = 10
    }

    public static void main(String[] args) {
        new Demo().test();
    }
}

 

⚙️ 익명 클래스 내부에서 멤버 선언 및 접근

익명 클래스에는 멤버와 관련하여 로컬 클래스와 동일한 제한 사항이 있다.

  • 익명 클래스에서는 정적 초기화 프로그램이나 멤버 인터페이스를 선언할 수 없다.
  • 익명 클래스는 상수 변수인 경우 정적 멤버를 가질 수 있다.

익명 클래스는 

  • 자신만의 필드
  • 추가 메서드(상위 유형의 메서드를 구현하지 않더라도)
  • 인스턴스 초기화(초기화 메서드)
  • 로컬 클래스

선언할 수 있다.
단, 생성자는 선언할 수 없다.(익명 클래스는 이름이 없기 때문에 생성자 정의가 불가능)

public interface HelloWorld {
    void Hello();
}
public class Main {
    public static void main(String... args) {
        // HelloWorld 인터페이스를 익명 클래스로 구현
        HelloWorld anonymous  = new HelloWorld() {

            // 익명 클래스의 멤버 변수
            private String name = "Bob";
			
            // 익명 클래스의 추가 메서드(고유 메서드)
            public void introduce() {
                System.out.println("[내부 메서드] 이름은 : " + name);
            }
			             
             @Override
             public void Hello() {
                 // 익명 클래스 멤버 변수 접근
                 System.out.println("안녕하세요, 제 이름은" + name + "입니다!!");				
			
                 // 익명 클래스 내부 메서드 호출
                 introduce();
            }			
        };
        anonymous.Hello();
     // anonymous.introduce(); // ❌ 컴파일 에러: HelloWorld 타입에는 존재하지 않음
    }
}

 

출력시

안녕하세요, 제 이름은Bob입니다!!
[내부 메서드] 이름은 : Bob

 

❗주의
익명 클래스를 익명타입(인터페이스 또는 추상 클래스 타입)으로 참조할 경우, 익명 클래스 안의 추가 메서드나 필드는 바깥에서 직접 접근할 수 없다

introduce() 메서드는 HelloWorld 인터페이스에는 존재하지 않는다!!

 

 

🧭 결론

익명 클래스는 이름 없이 정의되는 일회성 클래스이며,
짧고 단순한 인터페이스 구현이나 추상 클래스 상속에 매우 유용한 도구이다.

'JAVA > JAVA 기초 문법' 카테고리의 다른 글

Nested(중첩) Class  (1) 2025.05.28
Explicit(명시적)과 Implicit(암시적)  (0) 2025.05.14
Control Flow Statements  (1) 2025.05.14
Operators (연산자)  (0) 2025.04.30
Array(배열)  (0) 2025.04.23