본 게시글은 혼자 공부하는 자바 (저자 : 신용권)의 책과 유튜브 영상을 참고하였고, 개인적으로 정리하는 글임을 알립니다.


익명 객체

클래스를 선언할 때 일반적으로 클래스 이름과 동일한 소스 파일을 생성하고 클래스를 선언한다. 한번 선언해놓고 여러 곳에서 객체를 만들어 사용하고 싶을 때 간단히 클래스 이름으로 생성자를 호출할 수 있기 때문이다. 그런데 클래스 이름이 없는 객체도 있다. 이것을 익명(anonymous) 객체라고 한다.

 

클래스 상속에서 자식 클래스를 명시적으로 선언하는 이유는 어디서건 이미 선언된 자식 클래스로 간단히 객체를 생성해서 사용할 수 있기 때문이다. 이것을 재사용성이 높다고 하는데, 자식 클래스가 재사용되지 않고 오로지 특정 위치에서 사용할 경우라면 자식 클래스를 명시적으로 선언하는 것은 귀찮은 작업이 된다. 이 경우에는 익명 자식 객체를 생성해서 사용하는 것이 좋은 방법이다.

  • 익명 자식 객체 : 클래스를 상속해서 익명 객체를 만들 경우
  • 익명 구현 객체 : 인터페이스를 구현해서 만들 경우

 

익명 객체는 이름이 없는 객체를 말한다. 익명 객체를 만들려면 조건이 있다. 어떤 클래스를 상속하거나 인터페이스를 구현해야만 한다.

 

익명 자식 객체

new 부모생성자(매개값,...){
    //필드
    //메소드
}

위의 코드는 익명 자식 객체를 생성하는 코드인데, 부모생성자가 나왔다고 해서 부모 객체를 생성하는 것이 아니라 부모 클래스를 상속받되, 클래스의 이름은 없고, 클래스의 내용은 중괄호{} 안에 선언 하겠다는 뜻이다.

 

예제

Tire.java

public class Tire {
	public void roll() {
		System.out.println("일반 타이어가 굴러갑니다.");
	}
}

 

Car.java

public class Car {
	//필드에 Tire 객체 대입
	private Tire tire1 = new Tire();

	//필드에 익명 자식 객체 대입
	private Tire tire2 = new Tire() {
		@Override
		public void roll() {
			System.out.println("익명 자식 Tire 객체 1이 굴러갑니다.");
		}
	};

	//메소드(필드 이용)
	public void run1() {
		tire1.roll();
		tire2.roll();
	}

	//메소드(로컬 변수 이용)
	public void run2() {
		//로컬 변수에 익명 자식 객체 대입
		Tire tire = new Tire() {
			@Override
			public void roll() {
				System.out.println("익명 자식 Tire 객체 2가 굴러갑니다.");
			}
		};
		tire.roll();
	}

	//메소드(매개변수 이용)
	public void run3(Tire tire) {
		tire.roll();
	}
}

 

Main.java

public class Main {
	public static void main(String[] args) {
		//Car 객체 생성
		Car car = new Car();
		
		//익명 자식 객체가 대입된 필드 사용
		car.run1();
			
		//익명 자식 객체가 대입된 로컬변수 사용
		car.run2();
		
		//익명 자식 객체가 대입된 매개변수 사용
		car.run3(new Tire() {
			@Override
			public void roll() {
				System.out.println("익명 자식 Tire 객체 3이 굴러갑니다.");
			}
		});
	}
}
/*
일반 타이어가 굴러갑니다.
익명 자식 Tire 객체 1이 굴러갑니다.
익명 자식 Tire 객체 2가 굴러갑니다.
익명 자식 Tire 객체 3이 굴러갑니다.
*/

위의 예제를 통해 다음을 알 수 있다.

  • 필드값으로 익명 객체 대입할 수 있다.
  • 로컬 변수값으로 익명 객체 대입할 수 있다.
  • 매개값으로 익명 객체 대입할 수 있다.

 

심화 예제

Parent.java

public class Parent {
	int parentField;
	void parentMethod() {}
}

 

Child.java

public class Child extends Parent{} //상속

class A{
	Parent field = new Parent() { //익명 자식 객체 선언후 대입
		int anoyField;
		void anoyMethod() {System.out.println("anoyMethod()");}
		@Override
		void parentMethod() { //부모 메소드 오버라이딩
			anoyField = 3;
			System.out.println("anoyField : " + anoyField);
			anoyMethod();
		}
	};
	
	void method() {
		//field.anoyField = 3; 부모 클래스에서 선언되지 않았기 떄문이 불가
		//field.anoyMethod(); 부모 클래스에서 선언되지 않았기 떄문이 불가
		field.parentField = 3;
		System.out.println("parentField : " + field.parentField);
		field.parentMethod(); //익명 객체에서 오버라이딩 했기 때문에 가능
	}
}

 

Main.java

public class Main {
	public static void main(String[] args) {
		A a = new A();
		a.field.parentMethod();
		a.method(); //재정의된 메소드의 내용 출력
	}
}
/*
anoyField : 3
anoyMethod()
parentField : 3
anoyField : 3
anoyMethod()

*/

익명 자식 객체를 선언하는 중괄호 내부에는 필드나 메소드를 선언하거나 부모 클래스의 메소드를 오버라이딩하는 내용을 작성한다. 일반 클래스와 차이점은 생성자를 선언할 수 없다는 것이다.

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

 

public class Child extends Parent{}

class A{
	void method1(Parent parent) {}
	
	void method2() {
		method1(new Parent() {
			int anoyField;
			void anoyMethod() {}
			@Override
			void parentMethod() {}
		});
	}
}

위의 코드는 메소드의 매개 변수가 부모 타입일 경우, 메소드를 호출하는 코드에서 익명 자식 객체를 생성해서 매개값으로 대입하는 코드이다.

 

public class Person {
	void wake() {
		System.out.println("7시에 일어나자");
	}
}
public class Anonymous extends Person{
		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 Test {
	public static void main(String[] args) {
		Anonymous anony = new Anonymous();
		//익명 객체 필드 사용
		anony.field.wake();
		//익명 객체 로컬 변수 사용
		anony.method1();
		//익명 객체 매개값 사용
		anony.method2(
			new Person() {
				void study() {
					System.out.println("Study");
				}
				@Override
				void wake() {
					System.out.println("8시에 일어나자");
					study();
				}
			}
		);
	}
}
/*
6시에 일어나자
출근하자
7시에 일어나자
산책하자
8시에 일어나자
Study
*/

 


 

익명 구현 객체

new 인터페이스(){
    //필드
    //메소드
}

위 코드는 인터페이스로 객체를 생성하는 것이 아니라, 인터페이스를 구현할 익명 객체를 생성하는 코드이다.

당연히 인터페이스에서 추상 메소드로 선언된 메소드를 재정의 해야한다.

중괄호 블록 안의 필드와 메소드는 익명 구현 객체가 가져야할 멤버로, 중괄호 블록 안에서만 사용할 수 있다.

이렇게 생성된 구현 객체는 인터페이스 타입의 필드, 로컬벼누, 매개변수의 값으로 대입할 수 있다.

익명 구현 객체는 안드로이드와 같은 UI 프로그램에서 이벤트를 처리하는 객체로 많이 사용된다.

 

예제

RemoteControl.java

public interface RemoteControl {
	//추상 메소드
	void turnOn();
	void turnOff();
}

 

Home.java

public class Home {
	//필드에 익명 구현 객체 대입
	private RemoteControl rc = new RemoteControl() {
		@Override
		public void turnOn() {
			System.out.println("TV를 켭니다.");
		}
		@Override
		public void turnOff() {
			System.out.println("TV를 끕니다.");
		}
	};
		
	//메소드(필드 이용)
	public void use1() {
		rc.turnOn();
		rc.turnOff();
	}
	
	//메소드(로컬 변수 이용)
	public void use2() {
		//로컬 변수에 익명 구현 객체 대입
		RemoteControl rc = new RemoteControl() {
			@Override
			public void turnOn() {
				System.out.println("에어컨을 켭니다.");
			}
			@Override
			public void turnOff() {
				System.out.println("에어컨을 끕니다.");
			}
		};
		rc.turnOn();
		rc.turnOff();
	}
	
	//메소드(매개변수 이용)
	public void use3(RemoteControl rc) {
		rc.turnOn();
		rc.turnOff();
	}
}

 

Main.java

public class Main {
	public static void main(String[] args) {
		//Home 객체 생성
		Home home = new Home();

		//익명 구현 객체가 대입된 필드 사용
		home.use1();
		
		//익명 구현 객체가 대입된 로컬 변수 사용
		home.use2();

		//익명 구현 객체가 대입된 매개변수 사용
		home.use3(new RemoteControl() {
			@Override
			public void turnOn() {
				System.out.println("난방을 켭니다.");
			}
			@Override
			public void turnOff() {
				System.out.println("난방을 끕니다.");
			}
		});
	}
}
/*
TV를 켭니다.
TV를 끕니다.
에어컨을 켭니다.
에어컨을 끕니다.
난방을 켭니다.
난방을 끕니다.
*/