[JAVA] 익명(anonymous)객체

DANI·2023년 10월 5일
0

JAVA를 공부해보자

목록 보기
9/29
post-thumbnail

📕 익명(anonymous)객체란?

이름이 없는 객체를 뜻하며, 일시적으로 한 번만 사용하고 버려지는 객체를 의미한다.


❓ 익명(anonymous)객체를 사용 하는 이유?

  • 프로그램에서 일시적으로 한 번만 사용되고 버려지는 객체로서 UI 이벤트 처리, 스레드 객체등에 사용한다.

  • 재사용성이 없고, 확장성을 활용하는 것이 유지보수에서 더 불리할 때 사용


📖 익명(anonymous)객체 구현 방식

  1. 부모/자식 간 상속아래 익명 자식객체를 생성한다.
  2. 인터페이스를 구현한 익명 구현객체를 생성한다.

익명객체는 반드시 부모클래스나 구현할 인터페이스가 있어야 한다!


💻 1. 익명 자식객체 생성방식

부모클래스 [필드|변수] = new 부모클래스(매개값, ... ) {
	// 필드
    // 메소드
};

🔴 Anonymous 클래스 생성


// 클래스를 따로 생성해야하지만 편의상 같은 클래스에서 생성하겠음
// 부모 클래스
class Parents { 
	void wake() {
		System.out.println("7시에 일어납니다.");
	}
} 

public class Anonymous {
	
	// 1. 필드에 익명자식 객체를 생성
	Parents filed = new Parents() {
		void work() {
			System.out.println("출근합니다");
		}
		
		@Override
		void wake() {
			System.out.println("6시에 일어납니다");
			work();
		}
	};
	
	// 2. 로컬 변수 값으로 익명자식 객체를 생성
	void method() {
		Parents localVar = new Parents() {
			void walk() {
				System.out.println("산책을 합니다.");
			}
			
			@Override
			void wake() {
				System.out.println("8시에 일어납니다");
				walk();
			}
		};
		
		localVar.wake();
        
        // lovalVar.walk(); 
        // 익명 객체는 부모 클래스의 오버라이딩 된 메소드만 사용할 수 있음
	}
	
	// 3. 익명객체 매개변수로 대입
	void method2(Parents anonymous) {
		anonymous.wake();
	}
}

🔴 Main 클래스

public class AnonymousExample {

	public static void main(String[] args) {
		
		// 익명 객체 생성
		Anonymous anonymous = new Anonymous();
		
		
		// 익명 객체 필드 사용
		anonymous.filed.wake();

		
		// 익명 객체 로컬변수 사용
		anonymous.method();
		
		
		// 익명 객체 매개값 사용
		anonymous.method2(new Parents() {
			void eat() {
				System.out.println("밥을 먹습니다.");
			}
			
			@Override
			void wake() {
				System.out.println("11시에 일어납니다");
				eat();
			}
		});
	}
}

🔵 실행 결과

6시에 일어납니다
출근합니다
8시에 일어납니다
산책을 합니다.
11시에 일어납니다
밥을 먹습니다.

즉, 익명 객체는 부모 클래스의 메소드를 재정의 해서 사용해야만 한다.

💡 익명객체(익명클래스) 컴파일시 만들어지는 $ 클래스


코딩된 .java 파일은 자바 컴파일러를 통해 컴파일되고 .class 파일을 생성한다..

보통 .java 파일이 .class 파일과 1:1관계를 맺지만 익명객체(익명클래스)가 사용된 .java 파일을 컴파일 하게되면 .class 파일 뿐만 아니라 $.class를 생성한다.

즉, Anonymous.java라는 파일에서 익명객체를 2번 정의해서 Anonymous.class, Anonymous$1.class, Anonymous$2.class 이렇게 3개 클래스 파일이 생기고, AnonymousExample.java 파일에서 메서드 매개변수로 넘겨줄 때 익명객체를 1번 정의 하였으므로 AnonymousExample$1.class가 생긴다.

📂 경로에서 파일 확인해보기




💻 2. 익명 구현객체 생성방식

구현(implements) 이란 단어는 인터페이스(interface)와 연관되어있다. 즉, 인터페이스를 바탕으로 익명클래스(익명객체)를 구현할 수 있다.

보통 클래스 내부에 중첩인터페이스를 정의하고, 해당 클래스를 익명클래스(익명객체)로 정의할때 중첩인터페이스에서 강제적으로 정의해서 사용해야하는 메소드를 익명클래스 정의시점에 정의하여 사용한다. 일반 상속 익명 객체와 다른 점은 상속과 다르게 인터페이스는 강제적으로 무조건 정의를 통해 사용해야하는 메서드가 있기 때문에, 규격화에 도움이 된다.

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

🔴 RemoteControl 인터페이스 생성

public interface RemoteControl {
	public void turnON();
	public void turnOff();
}

🔴 Anonymous 클래스 생성

public class Anonymous {
	
	// 1. 인터페이스 타입으로 필드 초기값으로 대입
	RemoteControl field = new RemoteControl() {
		@Override
		public void turnOff() {
			System.out.println("TV를 켭니다(필드로 대입했습니다.)");
		}
		
		@Override
		public void turnON() {
			System.out.println("TV를 끕니다(필드로 대입했습니다.)");
		}
	};
	
	// 2. 로컬변수로 대입
	void method() {
		RemoteControl localVar = new RemoteControl() {
			@Override
			public void turnON() {
				System.out.println("Audio를 켭니다(로컬변수로 대입했습니다.)");
				
			}

			@Override
			public void turnOff() {
				System.out.println("Audio를 끕니다(로컬변수로 대입했습니다.)");
			}	
		};
		localVar.turnON();
		localVar.turnOff();
	}
	
	// 3. 매개 값으로 이용
	void method1(RemoteControl anonymous) {
		anonymous.turnON();
		anonymous.turnOff();
	}
}

🔴 Main 클래스

public class AnonymousExample {

	public static void main(String[] args) {
		
		Anonymous anonymous = new Anonymous();
		
		// 익명 객체 필드 사용
		anonymous.field.turnON();
		anonymous.field.turnOff();
		
		// 익명 객체 로컬 변수 사용
		anonymous.method();
		
		// 익명 객체 매개값 사용
		anonymous.method1(
				new RemoteControl() {
					@Override
					public void turnON() {
						System.out.println("mp3를 켭니다(매개값으로 대입했습니다.)");
						
					}

					@Override
					public void turnOff() {
						System.out.println("mp3를 끕니다(매개값으로 대입했습니다.)");
					}
				}
		);
	}
}

🔵 실행 결과

TV를 끕니다(필드로 대입했습니다.)
TV를 켭니다(필드로 대입했습니다.)
Audio를 켭니다(로컬변수로 대입했습니다.)
Audio를 끕니다(로컬변수로 대입했습니다.)
mp3를 켭니다(매개값으로 대입했습니다.)
mp3를 끕니다(매개값으로 대입했습니다.)

🔍 UI 프로그램에서 이벤트 처리 예시

화면에서 버튼(전원)을 누른다 -> 전원이 켜진다 -> 화면에서 버튼(전화)을 누른다 -> 전화를 건다

🔴 Button 클래스 생성

public class Button {
	
	// 중첩 인터페이스
	static interface OnclickListener {
		void Onclick();
	}
	
	// 인터페이스 타입 필드
	OnclickListener listener;
	
	// 매개 변수가 인터페이스 타입
	void setOnClickListener(OnclickListener listener) {
		this.listener = listener;
	}
	
	// 메소드
	void touch() {
		listener.Onclick();
	}
}

🔴 Window 클래스 생성

public class Window {
	
	Button onoff = new Button(); // 전원 버튼
	Button call = new Button(); // 전화 버튼
	
	// 필드의 초기값으로 대입
	Button.OnclickListener field = new Button.OnclickListener() {
		@Override
		public void Onclick() {
			System.out.println("전원을 켭니다");
		}
	};
	
	// 생성자
	Window(){
		
		onoff.setOnClickListener(field);
		
		// 매개값으로 대입
		call.setOnClickListener(new Button.OnclickListener() {
			@Override
			public void Onclick() {
				System.out.println("전화를 겁니다");
			}
		});
	}
}

🔴 Main 클래스

public class ButtonExample {

	public static void main(String[] args) {
		Window window = new Window();
	
		window.onoff.touch();
		window.call.touch();
	}
}

🔵 실행 결과

전원을 켭니다
전화를 겁니다


🚫 익명 객체의 로컬변수, 매개 변수 사용 시 주의할 점


익명 객체는 메소드 실행이 종료되면 없어지는 것이 일반적이지만, 익명 스레드 객체를 사용할 땐 메소드를 실행하는 스레드와 다르므로 메소드가 종료된 후에도 종료되지 않을 수 있다.

메소드의 매개 변수나 로컬 변수를 익명 객체에서 사용할 때 매개 변수나 로컬 변수는 스레드가 종료되면 스택메모리에서 사라지기 때문에 자바에서는 컴파일 할 때 이 값들을 익명 객체 내부에 복사해두고 사용한다.

이 때, 매개 변수나 로컬 변수의 값이 수정되면 익명 객체에 복사해둔 값과 달라지므로 final로 선언할 것을 요구한다.
(자바 8부터는 final로 선언하지 않더라도 final 특성을 부여해 준다.)


🔴 Calculate 인터페이스 생성

public interface Calculate {
	int sum();
}

🔴 Anonymous 클래스 생성

public class Anonymous {
	private int field;
	
	public void method(int arg1, int arg2) {
		int var1 = 0;
		int var2 = 1;
		
		field = 2;
		
		Calculate calc = new Calculate() {
			@Override
			public int sum() {
				
				// arg1 = 1; final의 특성을 지님
				// arg2 = 1; final의 특성을 지님
				
				// var1 = 2; final의 특성을 지님
				// var2 = 4; final의 특성을 지님
				
				field = 2;
				
				int result = arg1+arg2+var1+var2+field;
				return result;
			}
		};
		System.out.println(calc.sum());
	}
}

🔴 Main 클래스

public class AnonymousExample {
	public static void main(String[] args) {
		Anonymous anonymous = new Anonymous();
		anonymous.method(1, 2);
	}
}

🔵 실행 결과

6



Q. CheckBox 클래스에 중첩 인터페이스(OnSelectListener) 타입으로 필드(listener)를 선언하고 Setter 메소드(setOnSelectListener())로 외부에서 구현 객체를 받아 필드에 대입한다. 선택 이벤트가 발생했을때(select()메소드) 인터페이스를 통해 구현 객체의 메소드를 호출한다.(listener.onSelect())

🔵 실행결과

배경을 변경합니다

A. 내가 입력한 코드

🔴 CheckBox 클래스 생성

public class CheckBox {
	OnSelectListener listener;
	
	static interface OnSelectListener {
		void onSelect();
	}
	
	void setOnSelectListener(OnSelectListener listener) {
		this.listener = listener;
	}
	
	void select() {
		listener.onSelect();
	}
}

🔴 Main 클래스

import ch09.sec02.exam05.CheckBox.OnSelectListener;

public class Example {
	public static void main(String[] args) {
	
1.		CheckBox checkbox = new CheckBox();

2.		checkbox.setOnSelectListener(new OnSelectListener() {
3.			public void onSelect() {
4.				System.out.println("배경을 변경합니다");
			}
		});
		
5.		checkbox.select();
	}
}

매개변수에 익명 객체로 인터페이스 타입을 대입할 경우 import 해야하는 상황이 생긴다. import 된 상황도 크게 무리는 없지만, OnSelectListener의 경로가 어딘지 헷갈릴 수 있기 때문에 명확하게 하기 위해 new Checkbox.OnSelectListener()로 표현하자!


💯 수정 사항

2. checkbox.setOnSelectListener(new OnSelectListener() {
-> 2. checkbox.setOnSelectListener(new Checkbox.OnSelectListener() {

중첩인터페이스를 static으로 구현하였다. 따라서 import를 하지 않고 Checkbox 클래스를 이용하여 인터페이스 타입의 익명 객체를 생성할 수 있다.

0개의 댓글