Java 익명클래스

김소희·2025년 11월 12일

Java GUI의 탄생 배경

Java는 메모장, 그림판, 계산기, Eclipse와 같은 데스크톱 애플리케이션(CS 프로그램) 개발을 위해 설계되었다.

AWT vs Swing

1. AWT (Abstract Window Toolkit)

  • OS의 네이티브 컴포넌트를 사용
  • 운영체제가 제공하는 GUI 자원을 직접 활용
  • 플랫폼 의존적

2. Swing

  • 순수 Java로 구현된 컴포넌트
  • OS에 독립적
  • 더 풍부한 UI 제공

이벤트 처리 방식의 진화 과정

1단계: 별도 클래스 방식 - 생성자로 참조 전달

import java.awt.*;
import java.awt.event.*;

// 커스텀 프레임 클래스
class MyFrame extends Frame {
    public MyFrame(String title) {
        super(title);  // 부모 클래스(Frame)의 생성자 호출
    }
}

// 이벤트 핸들러 클래스 (재사용 가능하지만 일반적으로 재사용성 낮음)
class BtnClickHandler implements ActionListener {
    private TextField txtId;     // ❌ 필드 선언 필요
    private TextField txtPwd;    // ❌ 필드 선언 필요
    
    // 생성자로 외부 객체를 '전달받아야' 함
    public BtnClickHandler(TextField txtId, TextField txtPwd) {
        this.txtId = txtId;      // 참조를 저장
        this.txtPwd = txtPwd;    // 참조를 저장
    }
    
    @Override
    public void actionPerformed(ActionEvent event) {
        System.out.println("나 눌렀어!");
    }
}

// 메인 클래스
public class AwtFrame {
    public static void main(String[] args) {
        // 프레임 생성 및 설정
        MyFrame my = new MyFrame("Login");
        my.setSize(350, 350);
        my.setLayout(new FlowLayout());
        my.setVisible(true);
        
        // 버튼 컴포넌트 생성
        Button btn1 = new Button("첫 번째 버튼");
        Button btn2 = new Button("두 번째 버튼");
        Button btn3 = new Button("세 번째 버튼");
        
        // 이벤트 감지 및 연결
        BtnClickHandler handler = new BtnClickHandler(txtId, txtPwd);
        btn1.addActionListener(handler);
        
        // 프레임에 버튼 추가
        my.add(btn1);
        my.add(btn2);
        my.add(btn3);
    }
}

🔴 1단계의 특징과 문제점

접근 방식:

┌─────────────────┐
│  LoginForm      │
│  - txtId        │───┐
│  - txtPwd       │   │ 생성자로 전달
└─────────────────┘   │
                      ↓
┌─────────────────────────────┐
│  BtnHandler (별도 클래스)    │
│  - txtId  (전달받은 참조)   │
│  - txtPwd (전달받은 참조)   │
│  + BtnHandler(txtId, txtPwd)│
└─────────────────────────────┘

문제점:

  • 외부 객체(txtId, txtPwd)를 생성자 매개변수로 전달해야 함
  • 필드를 선언하고 참조를 저장해야 함
  • 코드가 복잡하고 길어짐
  • 재사용하지 않으면 불필요한 클래스 생성

2단계: Local Inner Class - Outer 클래스 직접 조회

import java.awt.*;
import java.awt.event.*;

class LoginForm2 extends Frame {
    Label lblId, lblPwd;
    TextField txtId, txtPwd;  // ← Outer 클래스의 멤버
    Button btnOk;
    
    public LoginForm2(String title) {
        super(title);
        
        // 컴포넌트 생성
        lblId = new Label("ID:", Label.RIGHT);
        lblPwd = new Label("pwd:", Label.RIGHT);
        txtId = new TextField(10);
        txtPwd = new TextField(10);
        txtPwd.setEchoChar('#');  // 비밀번호 마스킹
        btnOk = new Button("login");
        
        // 레이아웃 설정
        this.setLayout(new FlowLayout());
        this.setSize(500, 100);
        this.setVisible(true);
        
        // 컴포넌트 추가
        this.add(lblId);
        this.add(txtId);
        this.add(lblPwd);
        this.add(txtPwd);
        this.add(btnOk);
        
        // ===== Inner Class 활용 =====
        // 로컬 내부 클래스 (Local Inner Class)
        class BtnHandler implements ActionListener {
            // 필드 선언 불필요
            // 생성자 불필요
            // 장점: Outer Class의 필드에 직접 접근 가능
            
            @Override
            public void actionPerformed(ActionEvent e) {
                // txtId, txtPwd에 바로 접근 가능 (외부 클래스 멤버)
                // Outer 클래스의 txtId를 '직접 조회'
                String id = txtId.getText().trim();
                String pwd = txtPwd.getText();
                
                System.out.println(e.getSource());
                if (id.equals("hong")) {
                    System.out.println("방가 : " + id + " / " + pwd);
                } else {
                    System.out.println("배고픈 당신은 누구?");
                }
            }
        }
        
        // 이벤트 리스너 등록 - 전달 없이 그냥 생성
        btnOk.addActionListener(new BtnHandler());
        
        // ===== 윈도우 닫기 이벤트 처리 =====
        this.addWindowListener(new WindowListener() {
            @Override
            public void windowClosing(WindowEvent e) {
                e.getWindow().setVisible(false);
                e.getWindow().dispose();  // 메모리 해제
            }
            
            // 나머지 메서드들은 사용하지 않지만 구현 필수 (인터페이스)
            @Override
            public void windowOpened(WindowEvent e) {}
            @Override
            public void windowClosed(WindowEvent e) {}
            @Override
            public void windowIconified(WindowEvent e) {}
            @Override
            public void windowDeiconified(WindowEvent e) {}
            @Override
            public void windowActivated(WindowEvent e) {}
            @Override
            public void windowDeactivated(WindowEvent e) {}
        });
    }
}

public class Ex16_Button_Event_InnerClass {
    public static void main(String[] args) {
        LoginForm2 login = new LoginForm2("inner class");
    }
}

🟢 2단계의 핵심: 생성자 전달 → 직접 조회

접근 방식:

┌──────────────────────────────┐
│  LoginForm2                  │
│  - txtId                     │←─┐
│  - txtPwd                    │  │
│  ┌────────────────────────┐ │  │
│  │ BtnHandler (Inner)     │ │  │
│  │  (필드 없음)           │ │  │ 직접 조회
│  │  actionPerformed() {   │ │  │
│  │    txtId.getText() ────┼─┘  │
│  │    txtPwd.getText() ───┼────┘
│  │  }                     │ │
│  └────────────────────────┘ │
└──────────────────────────────┘

장점:

  • Inner Class가 Outer 클래스(LoginForm2)의 멤버를 직접 조회
  • 생성자로 전달할 필요 없음
  • 코드가 간결해짐
  • Inner Class는 Outer Class의 일부이기 때문에 모든 멤버 변수에 직접 접근 가능

왜 직접 조회가 가능한가?

  • Inner Class는 Outer의 모든 멤버 변수에 직접 접근 가능
  • this.txtId 같은 참조 없이도 바로 사용
  • 생성자로 "전달" 개념 자체가 사라짐

3단계: 익명 클래스 (Anonymous Class) 🎯

import java.awt.*;
import java.awt.event.*;

class LoginForm3 extends Frame {
    Label lblId, lblPwd;
    TextField txtId, txtPwd;
    Button btnOk;
    
    public LoginForm3(String title) {
        super(title);
        
        // 컴포넌트 초기화
        lblId = new Label("ID:", Label.RIGHT);
        lblPwd = new Label("pwd:", Label.RIGHT);
        txtId = new TextField(10);
        txtPwd = new TextField(10);
        txtPwd.setEchoChar('#');
        btnOk = new Button("login");
        
        // 레이아웃 설정
        this.setLayout(new FlowLayout());
        this.setSize(500, 100);
        this.setVisible(true);
        
        // 컴포넌트 추가
        this.add(lblId);
        this.add(txtId);
        this.add(lblPwd);
        this.add(txtPwd);
        this.add(btnOk);
        
        // ✨ 최종: 익명 클래스 (Anonymous Inner Class)
        // 클래스명 없이 즉석에서 구현
        btnOk.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String id = txtId.getText().trim();   // 직접 조회
                String pwd = txtPwd.getText();        // 직접 조회
                
                System.out.println(e.getSource());
                if (id.equals("hong")) {
                    System.out.println("방가 : " + id + " / " + pwd);
                } else {
                    System.out.println("배고픈 당신은 누구?");
                }
            }
        });
        
        // ✨ WindowAdapter 사용 (추상 클래스)
        // 필요한 메서드만 오버라이드
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                e.getWindow().setVisible(false);
                e.getWindow().dispose();  // 메모리 해제
            }
        });
    }
}

public class Ex17_Button_Event_Final {
    public static void main(String[] args) {
        LoginForm3 login = new LoginForm3("로그인");
    }
}

✨ 3단계의 핵심: 익명 클래스의 장점

1. 클래스 이름 불필요

// 이전: 클래스명 필요
class BtnHandler implements ActionListener { ... }
new BtnHandler();

// 익명: 클래스명 불필요
new ActionListener() { ... }

2. 즉석에서 구현

btnOk.addActionListener(new ActionListener() {
    // 여기서 바로 구현!
    @Override
    public void actionPerformed(ActionEvent e) {
        // 로직
    }
});

3. 코드가 한 곳에 모임

  • 이벤트 등록과 구현이 같은 위치
  • 가독성 향상
  • 유지보수 용이

4. 일회성 구현에 최적

  • 재사용하지 않는 리스너에 적합
  • 불필요한 클래스 파일 생성 방지
  • 군더더기 없는 깔끔한 코드

WindowListener vs WindowAdapter 비교

❌ WindowListener (인터페이스) - 비효율적

this.addWindowListener(new WindowListener() {
    // 🔴 7개 메서드 전부 구현 강제!
    @Override
    public void windowOpened(WindowEvent e) {}      // 안 쓰는데 구현
    
    @Override
    public void windowClosed(WindowEvent e) {}      // 안 쓰는데 구현
    
    @Override
    public void windowIconified(WindowEvent e) {}   // 안 쓰는데 구현
    
    @Override
    public void windowDeiconified(WindowEvent e) {} // 안 쓰는데 구현
    
    @Override
    public void windowActivated(WindowEvent e) {}   // 안 쓰는데 구현
    
    @Override
    public void windowDeactivated(WindowEvent e) {} // 안 쓰는데 구현
    
    @Override
    public void windowClosing(WindowEvent e) {
        // 그러나 실제로 사용하는 메서드는 이것뿐!
        e.getWindow().dispose();
    }
});

단점:

  • 인터페이스는 모든 메서드를 구현해야 함
  • 1개만 쓰는데 7개를 구현 → 비생산적
  • 코드가 불필요하게 길어짐

✅ WindowAdapter (추상 클래스) - 효율적

this.addWindowListener(new WindowAdapter() {
    // ✨ 필요한 메서드만 오버라이드!
    @Override
    public void windowClosing(WindowEvent e) {
        e.getWindow().setVisible(false);
        e.getWindow().dispose();
    }
    // 나머지 6개 메서드는 구현 안 해도 됨!
});

장점:

  • WindowAdapter는 모든 메서드를 빈 구현으로 제공
  • 필요한 메서드만 선택적으로 오버라이드
  • 코드가 간결하고 깔끔

Adapter 패턴의 원리

// WindowListener 인터페이스 (7개 추상 메서드)
interface WindowListener {
    void windowOpened(WindowEvent e);
    void windowClosed(WindowEvent e);
    void windowClosing(WindowEvent e);
    void windowIconified(WindowEvent e);
    void windowDeiconified(WindowEvent e);
    void windowActivated(WindowEvent e);
    void windowDeactivated(WindowEvent e);
}

// WindowAdapter 추상 클래스 (모든 메서드를 빈 구현)
abstract class WindowAdapter implements WindowListener {
    public void windowOpened(WindowEvent e) {}       // 빈 구현
    public void windowClosed(WindowEvent e) {}       // 빈 구현
    public void windowClosing(WindowEvent e) {}      // 빈 구현
    public void windowIconified(WindowEvent e) {}    // 빈 구현
    public void windowDeiconified(WindowEvent e) {}  // 빈 구현
    public void windowActivated(WindowEvent e) {}    // 빈 구현
    public void windowDeactivated(WindowEvent e) {}  // 빈 구현
}

// 사용 시: 필요한 것만 오버라이드
new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
        // 이것만 구현하면 됨!
    }
}

전체 진화 과정 비교표

진화 단계 요약

// 1단계: 별도 클래스 → 생성자로 전달
BtnHandler handler = new BtnHandler(txtId, txtPwd);
btn.addActionListener(handler);

// 2단계: Local Inner Class → 직접 조회
class BtnHandler implements ActionListener { ... }
btn.addActionListener(new BtnHandler());

// 3단계: 익명 클래스 → 즉석 구현 ✨
btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // 구현
    }
});

상세 비교

별도 클래스 방식

class BtnHandler implements ActionListener {
    private TextField txtId, txtPwd;  // 필드 선언
    
    public BtnHandler(TextField txtId, TextField txtPwd) {  // 생성자
        this.txtId = txtId;
        this.txtPwd = txtPwd;
    }
    
    public void actionPerformed(ActionEvent e) {
        txtId.getText();  // 저장된 참조 사용
    }
}
btn.addActionListener(new BtnHandler(txtId, txtPwd));

특징: 재사용 가능, 코드 분리, 생성자 전달 필요

Local Inner Class

class BtnHandler implements ActionListener {
    // 필드 없음, 생성자 없음
    public void actionPerformed(ActionEvent e) {
        txtId.getText();  // 직접 조회
    }
}
btn.addActionListener(new BtnHandler());

특징: 직접 조회, 클래스명 필요, 한 단계 간소화

익명 클래스 ✨

btn.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        txtId.getText();  // 직접 조회
    }
});

특징: 클래스명 불필요, 즉석 구현, 군더더기 없음


종합 비교표

구분별도 클래스Inner Class익명 클래스
접근 방법생성자로 참조 전달Outer 멤버 직접 조회Outer 멤버 직접 조회
필드 선언필요불필요불필요
생성자필요 (매개변수 전달)불필요불필요
클래스명필요필요불필요 ✨
코드량많음중간적음
재사용성높음낮음없음 (일회용)
결합도낮음 (독립적)높음 (Outer 의존)높음 (Outer 의존)
가독성분산집중매우 집중 ✨
사용처재사용 가능한 핸들러해당 클래스 전용일회성 이벤트

익명 클래스 사용 가능 대상

인터페이스 구현

new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        // 구현
    }
}

추상 클래스 구현

new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
        // 구현
    }
}

일반 클래스 상속도 가능 (잘 안 씀)

new Thread() {
    @Override
    public void run() {
        // 구현
    }
}

핵심 개념 정리

ActionListener

  • 버튼 클릭 등의 액션 이벤트 처리 인터페이스
  • actionPerformed(ActionEvent e) 메서드 구현 필수

TextField

  • 텍스트 입력 필드
  • getText(): 입력값 가져오기
  • setEchoChar(): 비밀번호 등의 입력값을 특정 문자로 마스킹

FlowLayout

  • 컴포넌트를 순서대로 배치하는 레이아웃
  • add() 순서대로 화면에 표시됨

Inner Class의 장점

  • 직접 조회: Outer 클래스의 멤버를 생성자 전달 없이 바로 접근
  • 코드 간소화: 필드 선언, 생성자 불필요
  • 캡슐화: 해당 클래스 내부에서만 사용

익명 클래스의 장점

  • 클래스명 불필요: 이름 없이 즉석에서 구현
  • 즉석 구현: 사용하는 곳에서 바로 정의
  • 일회성: 재사용하지 않는 코드에 최적
  • 깔끔함: 군더더기 없는 간결한 코드

Adapter 패턴

  • 인터페이스의 모든 메서드를 빈 구현으로 제공하는 추상 클래스
  • 필요한 메서드만 선택적으로 오버라이드
  • WindowAdapter, MouseAdapter, KeyAdapter 등

더 나아가기: 람다식 (Java 8+)

익명 클래스를 더욱 간결하게 표현할 수 있다.

익명 클래스

btnOk.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        String id = txtId.getText().trim();
        System.out.println(id);
    }
});

람다식으로 더 간결하게 ✨

btnOk.addActionListener(e -> {
    String id = txtId.getText().trim();
    System.out.println(id);
});

// 한 줄이면 중괄호 생략 가능
btnOk.addActionListener(e -> System.out.println(txtId.getText()));

최종 결론

이벤트 처리 진화의 핵심

  1. 별도 클래스: 생성자로 참조를 전달
  2. Inner Class: Outer 멤버를 직접 조회
  3. 익명 클래스: 클래스명 없이 즉석 구현 🎯

익명 클래스가 최적인 이유

  • 추상 클래스와 인터페이스를 직접 구현 가능
  • 군더더기 없는 깔끔한 코드
  • 일회성 이벤트 처리에 최적화
  • 코드 가독성과 유지보수성 향상
profile
개발자 소희의 노트

0개의 댓글