본문 바로가기
프로그래밍언어/Java

09-2 익명 객체

by 스꼬맹이브로 2021. 12. 14.
728x90
반응형
SMALL

익명(anonymous)객체는 이름이 없는 객체를 말한다. 

익명 객체를 만들려면 조건이 따르며, 조건이란 클래스를 상속하거나 인터페이스를 구현하는 것을 말한다.

 

익명 객체가 아닌 일반적인 경우에는 다음과 같이 명시적으로 클래스 이름을 주고 선언한다.

//일반적인 경우
//[상속]
class 클래스이름1 extends 부모클래스 {...}
부모클래스 변수 = new 클래스이름1();

//[구현]
class 클래스이름2 implements 인터페이스 {...}
인터페이스 변수 = new 클래스이름2();

이 경우 부모 클래스 변수는 클래스이름1의 객체를 참조하며 인터페이브 변수는 클래스이름2의 객체를 참조한다.

 

그러나 익명 객체를 생성할 때에는 다음과 같이 클래스 이름이 존재하지 않는다.

//익명 객체
//[상속]
부모클래스 변수 = new 부모클래스() {...};

//[구현]
인터페이스 변수 = new 인터페이스(){...};

이 경우 부모 클래스 변수는 이름이 없는 자식 객체를 참조하고, 인터페이스 변수는 이름이 없는 구현 객체를 참조한다.

 

<익명 자식 객체 생성>

먼저 부모 타입의 필드(또는 변수) 선언 후 자식 객체를 초기값으로 대입하는 경우를 생각해보자.

첫 번째로, 부모 클래스를 상속해서 자식 클래스를 선언한다. 

두 번째로 new 연산자를 이용해서 자식 객체를 생성한다.

마지막으로 부모 타입의 필드(또는 변수)에 대입한다.

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

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

위와 같은 방식이 일반적인 사용 방법이다. 

이렇게 자식 클래스를 명시적으로 선언하는 이유는 이미 선언된 자식 클래스는 어디에서든 간단히 객체를 생성해서 사용할 수 있기 때문이다. 즉, 재사용성이 높아지기 때문에 명시적으로 선언해준다.

 

그러나 자식 클래스가 재사용되지 않고, 특정 위치에서만 사용할 경우라면 명시적으로 선언하는 것은 필요없는 작업이 된다. 이러한 경우에 익명 자식 객체를 사용하는 것이 좋은 방법이다.

익명 자식 객체 생성방법은 다음과 같다.

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

'부모 클래스(매개값, ...)'은 부모 생성자를 호출하는 코드로 매개값은 부모 생성자의 매개 변수에 맞게 입력하면 된다.

중괄호 {}의 내부에는 필드나 메소드를 선언하거나 부모 클래스의 메소드를 재정의(오버라이딩)하는 내용을 작성한다.

'부모 클래스(매개값, ...){...}'은 부모 클래스를 상속해서 중괄호와 같이 자식 클래스를 선언하라는 뜻이다.

여기에 new 연산자를 통해 선언된 자식 클래스를 객체로 생성한다.

 

다음은 익명 자식 객체 생성에 대한 예시이다.

public class Person {
	void wake() {
		System.out.println("7시에 일어납니다.");
	}
}

Person이라는 이름의 부모 클래스 작성

public 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();
	}
}

필드 값으로 work함수 정의와 wake함수를 재정의하여 Person 클래스를 대입

method1()에 로컬변수값으로 walk함수 정의와 wake함수를 재정의하여 Person클래스를 대입

method2()에 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();
				}
			}
		);
	}
}

1. 필드 사용

2. 로컬변수 사용

3. 객체 매개값 새로 정의 후 사용

 

결과 :

 

<익명 구현 객체 생성>

인터페이스 타입의 필드 또는 변수를 선언하고, 구현 객체를 초기값으로 대입하는 경우를 생각해보자.

첫 번째로, 구현 클래스를 선언한다.

두 번째로, new 연산자를 이용해서 구현 객체를 생성한다.

세 번째로, 인터페이스 타입의 필드 또는 로컬 변수에 대입한다.

//일반적인 경우
class TV implements RemoteControl { }

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

다음과 같이 작성하는 것이 일반적이다.

이렇게 구현 클래스를 명시적으로 선언하는 이유는 자식 클래스를 선언하는 이유와 마찬가지로 어디서든 이미 선언된 구현클래스로 간단히 객체를 생성해서 사용할 수 있어 재사용성이 높기 때문이다.

 

하지만 특정 위치에서만 사용할 경우라면 구현 클래스를 명시적으로 선언하지 않고 익명 구현 객체를 생성해서 사용하는 것이 좋다.

익명 구현 객체를 생성하는 방법은 다음과 같다.

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

'인터페이스(){...}'는 인터페이스를 구현해서 중괄호{}와 같이 클래스를 선언하라는 뜻이며 new 연산자를 통해 객체로 생성한다. 

중괄호 {}에는 인터페이스에 선언된 모든 추상 메소드의 실체 메소드를 재정의해야 한다. 그렇지 않으면 컴파일 에러가 발생한다.

여기서 추가로 필드와 메소드를 선언할 수 있지만 실체 메소드에서만 사용이 가능하며 외부에서는 사용이 불가능하다.

 

다음은 익명 구현 객체 생성 예시이다.

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

인터페이스 생성

public class Anonymous {
	//필드 초기값으로 대입
	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();
	}
}

필드에 인터페이스 재정의 후 대입

method1()에 로컬변수값으로 인터페이스 재정의후 대입

method2()에 인터페이스 사용

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

1. 필드 사용

2. 로컬변수 사용

3. 객체 매개값 새로 정의 후 사용

 

결과 : 

<익명 객체의 로컬 변수 사용>

메소드의 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때도 제한이 따른다.

익명 객체는 메소드 실행이 종료되면 없어지는 것이 일반적이지만 익명 스레드 객체를 사용하는 경우 메소드가 종료되어도 계속 실행 상태로 존재할 수 있다.

여기서 문제는 메소드의 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때 나타난다.

매개 변수나 로컬 변수는 메소드 실행이 끝나면 스택 메모리에서 사라지기 때문에 익명 객체에서 지속적으로 사용이 불가능하기 때문이다.

때문에 컴파일 시 익명 객체에서 사용하는 매개 변수나 로컬 변수의 값을 익명 객체 내부에 복사한 후 사용하며, 매개 변수나 로컬 변수가 수정되어 값이 변경되면 익명 객체에 복사해 둔 값과 달라지기 때문에 매개 변수나 로컬 변수를 final로 선언할 것을 요구한다.

그래서 자바 7 이전까지는 final 키워드가 없으면 컴파일 에러가 발생했지만자바 8부터는 final 키워드가 없어도 값이 수정되지 않도록 final 특성을 알아서 부여해준다.

 

다음은 매개 변수와 로컬 변수가 익명 객체 내부에서 사용할 때 매개 변수와 로컬 변수가 final 특성을 갖고 있음을 보여주는 예시이다.

public interface Calculatable {
	public int sum();
}

인터페이스 Calculatable 선언

public class Anonymous {
	private int field;
	
	public void method(final int arg1, int arg2) {
		final int var1 = 0;
		int var2 = 0;
		
		field = 10;
		
		//arg1 = 20; (x)
		//arg2 = 20; (x)
		
		//var1 = 30; (x)
		//var2 = 30; (x)
		
		Calculatable calc = new Calculatable() {
			@Override
			public int sum() {
				int result = field + arg1 + arg2 + var1 + var2;
				return result;
			}
		};
		
		System.out.println(calc.sum());
	}
}

method함수 인자로 arg1, arg2값 전달해서 result 계산

method함수 안에서 var1, var2를 final변수로 선언하여 다시 지정 불가

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

method인자로 0, 0 전달

 

결과 :

728x90
반응형
LIST

'프로그래밍언어 > Java' 카테고리의 다른 글

10-2 예외 처리  (0) 2021.12.27
10-1 예외 클래스  (0) 2021.12.27
09-1 중첩 클래스와 중첩 인터페이스 소개  (0) 2021.06.29
08-2 타입 변환과 다형성  (0) 2021.06.28
08-1 인터페이스  (0) 2021.05.18