[Java]익명 객체

Devlog·2024년 3월 22일

Java

목록 보기
22/41

✔️ 익명 객체

일반적인 경우
[상속]
class 클래스이름1 extends 부모클래스 { ··· }
부모클래스 변수 = new 클래스이름1();
→ 부모 클래스 변수는 클래스1의 객체를 참조


[구현]
class 클래스이름2 implements 인터페이스 { ··· }
인터페이스 변수 = new 클래스이름2();
→ 인터페이스 변수는 클래스이름2의 객체를 참조

익명 객체 생성
[상속]
부모클래스 변수 = new 부모클래스() { ··· };
→ 부모 클래스 변수는 이름이 없는 자식 객체를 참조
[구현]
부모클래스 변수 = new 부모클래스() { ··· };
→ 인터페이스 변수는 이름이 없는 구현객체를 참조

: 클래스를 선언할 때 일반적으로 클래스 이름과
  동일한 소스 파일을 생성하고 클래스 선언함
: 한번 선언 선언해놓고 여러 곳에서 객체를 만들어 사용하고 싶을때
  간단히 클래스 이름으로 생성자를 호출할 수 있기 때문
: 클래스 이름이 없는 객체도 있음 → 익명 객체
: 어떤 클래스를 상속하거나 인터페이스를 구현해야만 함


✔️ 익명 자식 객체 생성

부모 타입의 필드 또는 변수를 선언하고
  자식 객체를 초기값으로 대입하는 경우
1) 부모 클래스를 상속해서 자식 클래스를 선언함
2) new 연산자를 이용해서 자식 객체를 생성한 후
  부모 타입의 필드 또는 변수에 대입하는 것

/** 자식 클래스 선언 **/
class Child extends Parent { }  

class A { 
	/** 필드에 자식 객체를 대입 **/
	Parent field = new Childe();
	
	void method() {
		/** 로컬 변수에 자식 객체를 대입 **/
		Parent localVar = new Child();
	}
}

• 자식 클래스를 명시적으로 선언하는 이유
→ 어디서건 이미 선언된 자식 클래스로 간단히
  객체를 생성해서 사용할 수 있기 때문
→ 이것을 재사용성이 높다고 말함

• 익명 자식 객체를 생성해야 하는 경우
→ 자식 클래스가 재사용 되지 않고
  오로지 특정 위치에서 사용할 경우라면
  자식 클래스를 명시적으로 선언해야할 때
  사용 함

[익명 자식 객체를 생성하는 방법]
부모클래스 [필드|변수] = new 부모클래스(매개값, ···) {
  //필드
  //메소드
};

: '부모 클래스(매개값, ···) {···}'은
  부모 클래스를 상속해서 중괄호 {}와 같이
  자식 클래스를 선언하라는 의미
: new연산자는 이렇게 선언된
  자식 클래스를 객체로 생성함
: '부모 클래스(매개값, ···) {···}'은 부모 생성자를 호출하는 코드로
  매개값은 부모 생성자의 매개 변수에 맞게 입력하면 됨
: 중괄호 { } 내부에는 필드나 메소드를 선언하고나
  부모 클래스의 메소드를 재정의(오버라이딩)하는 내용을 작성
: 일반 클래스와의 차이점은 생성자를 선언할 수 없음

- 필드를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입하는 예
ex)
class A {
	// A클래스의 필드 선언
	Parent field = new Parent() {
		int childField;
		void childMethod() { }
		// Parent의 메소드를 재정의
		@Override
		void parentMethod() { }
	};
}

- 메소드 내에서 로컬 변수를 선언할 때 초기값으로
  익명 자식 객체를 생성해서 대입하는 예
ex)
class A {
	void method() {
		// 로컬 변수 선언
		Parent localVar = new Parent() {
			int childField;
			void childMethod() { }
			// Parent의 메소드를 재정의
			@Override
			void parentMethod() { }
		};
	}
}

- 메소드의 매개 변수가 부모 타입일 경우
  메소드를 호출하는 코드에서 익명 자식 객체를 
  생성해서 매개값으로 대입하는 예
class A {
	void method1(Parent parent) { }
	
	void method2() {
		//method1()메소드를 호출
		method1(
			//method1()의 매개값으로
			  익명 자식 객체를 대입
			new Parent() {
				int childField;
				void childMethod()() { }
				@Override
				void parentMethod() { }
			}
		};
	}
}

- 익명 자식 객체에 새롭게 정의된 필드와 메소드는
  익명 자식 객체 내부에서만 사용됨
  외부에서는 접근할 수 없음
→ 왜냐하면 익명 자식 객체는 부모 타입 변수에 대입되므로
  부모 타입에 선언된것만 사용할 수 있기 때문

ex)
childField 필드와 childMethod()메소드는
parentMethod()메소드내에서 사용이 가능하나,
A 클래스의 필드인 field로는 접근할 수 없음

class A {
	Parent field = nw Parent() {
		int childField;
		void childMethod() { }
		@Override
		void parentMethod() {
			childField = 3;
			childMethod();
		}
	};

	void method() {
		field,childField = 3;
		field.childMethod();
		field.parentMethod();
	}
}
//부모클래스
class Person {
	void wake() {
		System.out.println("7시에 일어납니다.");
	}
}

//익명 자식 객체 생성
class Anonymous {
	//필드 초기값으로 대입
	//필드값으로 익명 객체 대입
	Person field = new Person() {
		void work() {
			System.out.println("출근합니다.");
		}
		
		@Override
		void wake() {
			System.out.println("6시에 일어납니다.");
			work();
		}
	};
	
	void method1() {
		//로컬 변수값으로 대입
		//로컬 변수값으로 익명 객체 대입
		Person localVar = new Person() {
			void walk() {
				System.out.println("산책합니다.");
			}
			@Override
			void wake() {
				System.out.println("7시에 일어납니다.");
				walk();
			}
		};
		
		//로컬 변수 사용
		localVar.wake();
	}
	
	void method2(Person person) {
		person.wake();
	}
}

//익명 자식 객체 생성
public class AnonymousExample {
	public static void main(String[] args) {
		Anonymous anony = new Anonymous();
		//익명 객체 필드 사용
		anony.field.wake();
		//익명 객체 로컬 변수 사용
		anony.method1();
		//익명 객체 매개값 사용
		anony.method2(
				//매개값으로 익명 객체 대입
				new Person() {
					void study() {
						System.out.println("공부합니다.");
					}
					@Override
					void wake() {
						System.out.println("8시에 일어납니다.");
						study();
					}
				}
		);
	}
}

💻 결과
6시에 일어납니다.
출근합니다.
7시에 일어납니다.
산책합니다.
8시에 일어납니다.
공부합니다.

✔️ 익명 구현 객체 생성

- 일반적으로 인터페이스 타입의 필드 또는 변수를 선언하고,
  구현 객체를 초기값으로 대입하는 경우를 생각해보면
1) 구현 클래스를 선언함
2) new 연산자를 이용해서 구현 객체를 생성
3) 인터페이스 타입의 필드 또는 로컬 변수에 대입

class TV implements RemoteControl { }

class A {
	//필드에 구현 객체를 대입
	RemoteControl field = new TV(); 
	void method() {
		//로컬 변수에 구현 객체를 대입
		RemoteControl localVar = new TV();
   	}
}

• 자식 클래스를 명시적으로 선언하는 이유
→ 어디서건 이미 선언된 구현 클래스로
  간단히 객체를 생성해서 사용할 수 있기 때문
→ 이것을 재사용성이 높다고 말함

• 익명 구현 객체를 생성해야 하는 경우
→ 구현 클래스가 재사용되지 않고,
  오로지 특정 위치에서 사용할 경우라면
  구현 클래스를 명시적으로 선언하지 않고
  익명 구현 객체를 생성해서 사용하는 것이 좋음

[익명 자식 객체를 생성하는 방법]
인터페이스 [필드|변수] = new 인터페이스() {
  //인터페이스에 선언된 추상 메소드의 실체 메소드 선언
  //필드
  //메소드
};

: '인터페이스() { ··· }'는 인터페이스를 구현해서
  중괄호 {}와 같이 클래스를 선언하라는 뜻
: new 연산자는 이렇게 선언된 구현 클래스를
  객체로 생성함
: 중괄호 {}에는 인터페이스에 선언된 모든 추상 메소드의
  실체 메소드를 작성(재정의)해야 함
  그렇지 않으면 컴파일 에러가 발생
: 필드와 메소드를 선언할 수 있지만,
  실체 메소드에서만 사용이 가능하고
  외부에서는 사용할 수 없음

- 필드를 선언할 때 초기값으로 익명 구현 객체를 생성해서 대입하는 예
ex)
class A {
	//클래스 A의 필드 선언
	RemoteControl field  = new RemoteControl() {
		//RemoteControl 인터페이스의
  		//추상 메소드에 대한 실체 메소드
		@Override
		void turnOn() { }
	};
}

- 메소드 내에서 로컬 변수를 선언할 때
  초기값으로 익명 구현 객체를 생성해서 대입하는 예
ex)
void method() {
	//로컬 변수 선언
	RemoteControl localVar = new RemoteControl() {
		//RemoteControl 인터페이스의
		//추상메소드에 대한 실체 메소드
		@Override
		void turnOn() { }
	}
}

- 메소드의 매개 변수가 인터페이스 타입일 경우
  메소드를 호출하는 코드에서 익명 구현 객체를 생성해서
  매개값으로 대입하는 예
ex)
class A {
	void method1(RemoteControl rc) { }
	
	void method2() {
		method1(
			new RemoteControl() {
				@Override
				void turnOn() { }
			}
		);
	}
}
// 인터페이스
interface RemoteControl {
	public void turnOn();

	public void turnOff();
}

// 익명 구현 객체 생성
class Anonymous2 {
	// 필드 초기값으로 대입
	RemoteControl field = new RemoteControl() {
		// 필드선언과 초기값 대입
		@Override
		public void turnOn() {
			System.out.println("TV를 켭니다.");
		}

		@Override
		public void turnOff() {
			System.out.println("TV를 끕니다.");
		}
	};

	void method1() {
		// 로컬 변수값으로 대입
		RemoteControl localVar = new RemoteControl() {
			// 로컬 변수 선언과 초기값 대입
			@Override
			public void turnOn() {
				System.out.println("Audio를 켭니다.");
			}

			@Override
			public void turnOff() {
				System.out.println("Audio를 끕니다.");
			}
		};
		// 로컬 변수 사용
		localVar.turnOn();
	}

	void method2(RemoteControl rc) {
		rc.turnOn();
	}
}

// 익명 구현 객체 생성
public class AnonymouseExample2 {
	public static void main(String[] args) {
		Anonymous2 anony2 = new Anonymous2();
		// 익명 객체 필드 사용
		anony2.field.turnOn();
		// 익명 객체 로컬 변수 사용
		anony2.method1();
		// 익명 객체 매개값 사용
		anony2.method2(
				new RemoteControl() {
					@Override
					public void turnOn() {
						System.out.println("SmartTV를 켭니다.");
					}
					
					@Override
					public void turnOff() {
						System.out.println("SmartTV를 끕니다.");
					}
				}
		);
	}
}

💻 출력
TV를 켭니다.
Audio를 켭니다.
SmartTV를 켭니다.
- UI 클래스
: Button 클래스의 내용을 보면 중첩 인터페이스(OnClickListener) 타입으로
  필드(listener)를 선언하고 Setter 메소드(setOnClickListener())로 외부에서
  구현 객체를 받아 필드에 대입함
: 버튼 이벤트가 발생했을 때(touch()메소드가 실행되었을 때)
  인터페이스를 통해 구현 객체의 메소드를 호출(listener.onClick())함

public class Button {
	// 인터페이스 타입 필드
	OnClickListener listener;

	// 매개변수의 다형성
	void setOnClickListener(OnClickListener listener) {
		this.listener = listener;
	}

	//구현 객체의 onClick() 메소드 호출
	void touch() {
		listener.onClick();
	}

	//중첩 인터페이스
	static interface OnClickListener {
		void onClick();
	}
}
/*
 * Window 클래스를 2개의 Button 객체를
 * 가지고 있는 창이라고 가정해보면
 * 
 * 첫 번째 button1의 클릭 이벤트 처리는
 * 필드로 선언한 익명 구현 객체가 담당
 * 
 * 두 번째 button2의 클릭 이번트 처리는
 * setOnClickListener()를 호출할때
 * 매개값으로 준 익명 구현 객체가 담당
 */
//UI 클래스
class Button {
	// 인터페이스 타입 필드
	OnClickListener listener;

	// 매개변수의 다형성
	void setOnClickListener(OnClickListener listener) {
		this.listener = listener;
	}

	//구현 객체의 onClick() 메소드 호출
	void touch() {
		listener.onClick();
	}

	//중첩 인터페이스
	static interface OnClickListener {
		void onClick();
	}
}

//UI 클래스
class Window{
	Button button1 = new Button();
	Button button2 = new Button();
	
	//필드 초기값으로 대입
	// 필드값으로 익명 객체 대입
	Button.OnClickListener listener = new Button.OnClickListener() {
		@Override
		public void onClick() {
			System.out.println("전화를 겁니다.");
		}
	};
	
	Window() {
		//매개값으로 필드 대입
		button1.setOnClickListener(listener);
		//매개값으로 익명 객체 대입
		button2.setOnClickListener(new Button.OnClickListener() {
			@Override
			public void onClick() {
				System.out.println("메시지를 보냅니다.");
			}
		});
	}
}

//실행클래스
public class Main {
	public static void main(String[] args) {
		Window w = new Window();
		// 버튼 클릭
		w.button1.touch();
		w.button2.touch();
	}
}

💻 결과
전화를 겁니다.
메시지를 보냅니다.

✔️ 익명 객체의 로컬 변수 사용

: 메소드의 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때 제한이 있음
  익명 객체는 메소드 실행이 종료되면 없어지는 것이 일반적이지만,
  메소드가 종료되어도 계속 실행 상태로 존재 할 수 있음

ex)
익명 스레드 객체를 사용할 때
매개 변수나 로컬 변수는 메소드 실행이 끝나면
스택 메모리에서 사라지기 때문에 익명 객체에서 지속적으로 사용할 수 없음

→ 이 문제의 해결방법은
  컴파일 시 익명 객체에서 사용하는
  매개 변수나 로컬 변수의 값을
  익명 객체 내부에 복사해두고 사용함
  그리고 매개 변수나 로컬 변수가 수정되어
  값이 변경되면 익명 객체에 복사해 둔 값과
  달라지므로 매개 변수나 로컬 변수를
  final로 선언할 것을 요구함

: 자바 7 이전까지는 final 키워드 필수
  자바 8 이후부터는 생략 가능
  final 선언을 하지 않아도 값이 수정될 수 없도록
  final의 특성을 부여 받았기 때문

``` java
/*
 * 매개 변수와 로컬 변수가 익명 객체 내부에서 사용할 때
 * 매개 변수와 로컬 변수가 final 특성을 갖고 있음을 보여줌
 */

// 인터페이스
interface Calculatable {
	public int sum();
}


// 익명 객체의 로컬 변수 사용
class Anonymous3 {
	private int field;
	
	public void method(final int arg1, int arg2) {
		final int var1 = 0;
		int var2 = 0;
		
		field = 10;
		
		//(x)
		//arg1 = 20;
		//arg2 = 20;
		
		//(x)
		//var1 = 30;
		//var2 = 30;
		
		Calculatable calc = new Calculatable() {
			@Override
			public int sum() {
				int result = field + arg1 + arg2 + var1 + var2;
				return result;
			}
		};
		
		System.out.println(calc.sum());
		
	}
}

public class AnonymousExample3 {
	public static void main(String[] args) {
		Anonymous3 anony3 = new Anonymous3();
		anony3.method(0, 0);
	}
}

💻 결과
10

0개의 댓글