programming language/Java

[Java] 익명 클래스(Anonymous Class)

공대키메라 2025. 4. 23. 22:14

지난번 Local Class(로컬 클래스) 에 이어서...

 

드디어...! 익명 클래스와 람다에 대해 알아보려고 한다.

 

원래는 단순하게 익명 클래스에 대해서만 알아보려고했는데, 람다가 자주 비교대상으로 쓰이기에 람다를 추가해서 보려니

 

관련 정보(inner class, local class, enclosing class 등...)에 관한 지식이 튀어나와서 

 

이를 먼저 알아보았다 휴...

 

이번에도 역시 공식문서를 중심으로 읽어나갈 계획이다.

 


 

1.  익명클래스 (Anonymous Class)

익명 클래스는 우리 코드를 더 정확하게 만들 수 있도록 한다. 동시에 클래스를 선언하고 인스턴스화 할 수 있도록 한다. 

마치 이름이 없는 지역 클래스(local class) 같다. 만약 로컬클래스를 한 번만 사용할거면 익명클래스를 사용하라.

 

그래! 이름이 없는 지역 클래스(local class) 라는 말 하나 때문에 앞에 선행학습을 했다.

 

결국 한 번만 사용할 로컬 클래스를 익명 클래스라고 하는데, 로컬 클래스가 뭐였지?

로컬클래스란?

로컬 클래스는 블록 안에 정의된 클래스이며,
블록이란 중괄호로 감싸진 0개 이상의 문(statement)들로 이루어진 코드 묶음을 말한다.
로컬 클래스는 일반적으로 메서드 본문 안에서 정의되는 경우가 많다.

 

한줄로 정리해보면...

 

익명 클래스는 표현식으로 이름이 없는 로컬 클래스이며 동시에 클래스를 선언하고 인스턴스화 할 수 있다.

 

(반박시 그대 말이 맞음...;;)

 

2.  익명 클래스 선언(Declaring Anonymous Classes)

로컬 클래스가 클래스를 선언하는 반면에, 익명 클래스는 표현식(expressions)이다. 이는 다른 표현식 안에서 클래스를 정의한다는 것을 의미한다.

 

다음 예시인 HelloWorldAnonymousClasses는 지역 변수 frenchGreeting과 spanishGreeting을 초기화 상태에서 익명클래스로 사용하고 있는데, 변수 englishGreeting 은 초기화를 위해 local class를 사용하고 있다.

 

HelloWorldAnonymousClasses.java

public class HelloWorldAnonymousClasses {
  
    interface HelloWorld {
        public void greet();
        public void greetSomeone(String someone);
    }
  
    public void sayHello() {
        
        class EnglishGreeting implements HelloWorld {
            String name = "world";
            public void greet() {
                greetSomeone("world");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hello " + name);
            }
        }
      
        HelloWorld englishGreeting = new EnglishGreeting();
        
        HelloWorld frenchGreeting = new HelloWorld() {
            String name = "tout le monde";
            public void greet() {
                greetSomeone("tout le monde");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Salut " + name);
            }
        };
        
        HelloWorld spanishGreeting = new HelloWorld() {
            String name = "mundo";
            public void greet() {
                greetSomeone("mundo");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hola, " + name);
            }
        };
        englishGreeting.greet();
        frenchGreeting.greetSomeone("Fred");
        spanishGreeting.greet();
    }

    public static void main(String... args) {
        HelloWorldAnonymousClasses myApp =
            new HelloWorldAnonymousClasses();
        myApp.sayHello();
    }

 

2. 익명 클래스의 문법(Syntax of Anonymous Class)

이전에 언급했듯이 익명 클래스는 표현식이다.

 

익명클래스 표현의 문법은 코드의 블록안에 포함된 클래스 정의를 제외하고는 생성자의 호출같다.

 

frenchGreeting 객체의 초기화를 보자.

 HelloWorld frenchGreeting = new HelloWorld() {
            String name = "tout le monde";
            public void greet() {
                greetSomeone("tout le monde");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Salut " + name);
            }
        };

 

익명클래스 표현식은 다음과 같이 구성되어 있다.

 

  • new 연산자(operator)
  • 구현하기위한 인터페이스 혹은 상속하는 클래스의 이름. 이 예에서, 익명 클래스는 HelloWorld 인터페이스를 구현한다.
  • 일반 클래스 인스턴스 생성 표현식처럼 생성자의 인수를 포함하는 괄호. 참고: 인터페이스를 구현할 때는 생성자가 없으므로, 이 예제처럼 빈 괄호 쌍을 사용합니다.
  • 클래스 선언 본문. 더 구체적으로, 본문(body) 안에 메소드 선언이 가능하지만 문장(statement)은 안된다.

익명클래스 정의가 표현식이기 떄문에, 문장의 일부여야만 한다. 

 

예를 들어, 익명 클래스 표현식은 frenchGreeting 객체를 인스턴스화한 문장의 일부분이다. (이게 왜 닫기괄호 뒤에 세미콜린이 붙는지에 대한 이유이다.)

 

 

Java에서는 문장과 표현식이 있다. 클래스를 만들 때에는

 

class TestClass{
	
}

 

뭐... 다음과 같이 끝나겠지만, 지금 익명 함수의 끝에는 세미콜론(;)이 붙었다.

 

이것은 표현식이기 때문에, 표현식이 변수 할당문의 일부로 사용될 때는 마지막에 세미콜론(;)를 붙여야 한다는 말이다.

 

 

3. 외부 범위의 로컬 변수 접근 및 익명 클래스의 멤버 선언과 접근

로컬 클래스와 같이, 익명 클래스는 변수를 붙잡을 수 있다.(capture variables) 

여기서 헷갈리는 것을 복기하기 위해서 다시 적는다.

capture 한다는 표현은 로컬 클래스 부분에서 언급했는데, 외부의 변수를 로컬 클래스에서 사용하는 것을 capture variable! 이라고 표현한다.

또 중간에 effectively final하다는 표현이 나오는데 변수를 할당하고 이를 바꾸지 않으면 effectively final 하다고 표현한다.

예를 들어 int testNumber  = 10; 로 선언 했는데, testNumber = 11; 같이 재할당을 하지 않는 것이다.

지난 로컬 클래스에서 encloding class 의 variable 에 접근 시 final or effectively final 이어야 한다고 했다.

또, 중첩 클래스 구조에서 inner class와 encloding class에 같은 이름의 변수를 선언하면 가려진 선언은 이름만으로는 참조할 수 없는 shadowing 이 발생한다. (그냥 바로 접근 못하니 잘하라는거)

이 정보를 상기시키며 다시 읽으면 눈에 귀신같이 들어온다! 이히히힣!

 

 

이처럼 익명클래스는 로컬 클래스처럼 외부 범위의 로컬 변수에 같은 접근 방식을 가지고 있다.

 

  • 익명 클래스는 enclosing클래스의 멤버에 접근할 수 있다.
  • 익명 클래스는 final 혹은 effectively final으로 선언되지 않은 encloding scope에 로컬 변수에 접근할 수 없다.
  • 중첩 클래스와 마찬가지로, 익명 클래스에서 선언된 타입(예: 변수)은 동일한 이름을 가진 둘러싸고 있는 범위의 다른 선언들을 가린다.

 

익명 클래슨느 또한 다른 멤버들에 대하여 로컬 클래스처럼 같은 제한을 가진다.

 

  • 익명 클래스 내부에 static 초기화 혹은 멤버 interface를 선언할 수 없다.
  • 익명 클래스는 constant variable로 제공되는 static member를 가질 수 있다.

다음과 같은 것은 선언이 가능핟.

  • Field(필드)
  • Extra Method(다른 메소드)
  • Instant initialiizer(인스턴스 초기자)
  • Local Class(로컬 클래스)

하지만, 익명 클래스에서 생성자는 선언할 수 없다.

 

4. 익명 클래스의 예시 (Example of Anonymous Class)

 

HelloWorld.java

import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
 
public class HelloWorld extends Application {
    public static void main(String[] args) {
        launch(args);
    }
    
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction(new EventHandler<ActionEvent>() {
 
            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });
        
        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

 

 

여기 예시를 보면 btn.setOnAction 내부에 parameter로 익명 클래스를 넘기고 있다.

 

EventHandler<AcitonEvent> 인터페이스는 하나의 메소드인 handle만 포함하고 있다.

 

새로운 클래스를 가지로 이 메소드를 구현하는거 보다는  이 예시를 익명 클래스 표현식을 사용한다. 

 

EventHandler<AcitonEvent> 인터페이스가 오직 하나의 메소드만 포함하기 대문에 익명 클래스 표현식 대신에 람다 표현식을 사용할 수 있다.

 

익명클래스는 두개 혹은 더 많은 메소드를 포함하고 있는 인터페이스를 구현할 때 이상적이라고 소개하고 있다.

 


 

이렇게 이번 시간에 익명 클래스가 무었인지, 어떤 특징이 있는지 그에 대한 예시를 살펴 보았다.

 

익명 클래스에 메소드가 하나라면 람다 표현식을 사용할 수 있다는 언급이 위에 되있었는데,

 

다음 시간에는 바로 이어서 람다에 대해 알아보고자 한다.

 

이렇게 파고 파고 읽어보니 서로 어느정도 겹치는 내용이 있어서, 좀 놓치면 이해가 안되는 부분들이 있으니 여러번 읽는게 좋을 거 같다.

 

 

출처:

https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html