[Java] 익명 객체

박세진·2021년 1월 24일
0
post-thumbnail

멀티 스레드에 대해 공부를 하던 중, Runnable 익명 객체를 매개값으로 사용하면 코드를 절약할 수 있다는 말을 보았다. 익명 객체에 대해 대충은 알고 있었지만 까먹었던 부분도 있고, 정리가 잘 되어있지 않은 것 같아 다시 공부를 하기로 결정했다.
<이것이 자바다> 책을 많이 참고하였다.


익명 객체란 ? 😲

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

  • 사용 목적 : UI 이벤트 처리 객체 / 스레드 객체를 간편하게 생성하기 위해 많이 활용
  • 사용 용도 : 필드 / 로컬변수 의 초기값, 매개변수의 매개값으로 사용
  • 단독으로 생성 불가, 클래스 상속 or 인터페이스 구현해서 사용 가능 !
  • 재사용 목적이 아닌 1번만 사용 하려고 할 때 쓴다.

1. 익명 자식 객체 생성

cf) 부모 타입으로 필드/변수를 선언하고, 자식 객체를 초기값으로 대입한다고 하자.
이때 해야할 일은

  • 부모클래스를 상속해서 자식 클래스 선언
  • new 연산자를 이용해 자식 객체 생성
  • 생성한 후 필드/ 로컬 변수에 대입

이다. 아래의 코드를 참고하자.

class Child Extends Parent {  }

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

하지만 자식 클래스가 재사용 되지 않고, 단지 필드/변수의 초기값으로만 사용할 경우라면, 익명 자식 객체를 생성하여 초기값으로 대입하는 것이 좋은 방법이다.
아래의 코드를 참고하자.

public class AnonymousEx {
	// 익명 자식 객체 생성시
	// 부모 클래스를 상속해서 중괄호 {} 의 내용과 같이 자식 클래스를 선언하라는 뜻. 
	// new 연산자는 이렇게 선언된 자식 클래스를 객체로 생성한다.
	Parent field = new Parent() { // 필드 선언시 초기값으로 익명 자식 객체를 생성해서 대입한것.
		int childField;
		
		// 부모 클래스의 메소드를 재정의 
		@Override
		void parentMethod() {
			
		}
		// 메소드 내에서 로컬 변수 선언시 초기값으로 익명 자식 객체를 생성해서 대입한당.
		void childMethod() {
			Parent localVar = new Parent() {
				int childField;
				@Override
				void parentMethod() {}
				
			};
		}
	};//익명 클래스의 선언은 하나의 실행문이므로 끝에 세미콜론(;)이 무조건 붙는다. (주의!!)
}

이때, 익명 자식 객체에 새롭게 정의된 필드와 메소드는 익명 자식 객체 내부에서만 사용되고, 외부에서는 필드와 메소드에 접근할 수 없다.
[why?🤔]
부모 타입의 변수에 대입 되기 때문에, 부모 타입에 선언된 것만 사용할 수 있기 때문이다.
아래의 코드를 참고로 설명하겠다.

public class AnonymousEx2 {
	    Parent p = new Parent() {
	        int childField;
	        void childMethod() { }
            
	        @Override
	        void parentMethod() {
	            childField = 3; // 익명 자식 객체 내부에서는 호출 가능
	            childMethod();  // 익명 자식 객체 내부에서는 호출 가능
	        }
	    };
	    
	    void method() {
	        p.childField = 3; // 사용 불가. p는 부모 타입이므로 부모 객체에 선언된 것만 사용가능하다.
	        p.childMethod();  // 사용 불가. p는 부모 타입이므로 부모 객체에 선언된 것만 사용가능하다.
	        p.parentMethod(); // 사용 가능.
	    }
	}

위의 코드에서, p는 부모타입의 변수 이므로 부모 객체에 선언된 것만 사용 가능하다.
따라서 childMethod(), childField는 사용할 수 없다.


2. 익명 구현 객체 생성

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

  • 구현 클래스를 선언
  • new 연산자를 이용해서 구현 객체 생성
  • 생성한 객체를 필드나 로컬 변수에 대입

이다.
익명 자식 객체 생성 때와 마찬가지로, 구현 클래스가 재사용 되지 않고 해당 필드 / 변수의 초기값으로만 사용하는 경우라면 익명 구현 객체를 초기값으로 대입하는 것이 좋다.

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

아래의 코드를 참고하자.

public interface RemoteControl {
	// 인터페이스 선언
	
	public void turnOn();
	public void turnOff();
}
public class AnonymousEx3 {
	RemoteControl field = new RemoteControl() {
		// 필드 초기값으로 대입
		
		@Override
		public void turnOn() {
			System.out.println("TV 켜기");
		}
		
		@Override
		public void turnOff() {
			System.out.println("TV 끄기");
		}
	}; // 세미콜론 잊지 말기 !
	void method() {
		// 로컬 변수 값으로 대입하기
		RemoteControl locVal = new RemoteControl() {
			
			@Override
			public void turnOn() {
				System.out.println("오디오 켜기");
			}
			
			@Override
			public void turnOff() {
				System.out.println("오디오 끄기");
			}
		};
		// 로컬 변수 사용하기
		locVal.turnOn();
	}
	void method2(RemoteControl rc) {
		rc.turnOn();
	}
}
public class AnonymousTest {
	public static void main(String[] args) {
		AnonymousEx3 anony = new AnonymousEx3();
		
		// 익명 객체 필드 사용하기
		anony.field.turnOn(); // TV 켜기 
		
		// 익명 객체 로컬변수 사용하기
		anony.method(); // 오디오 켜기
		
		// 익명 객체 매개값 사용하기
		anony.method2(new RemoteControl() {
			
			@Override
			public void turnOn() {
				System.out.println("smart TV 켜기");
			}
			
			@Override
			public void turnOff() {
				System.out.println("smart TV 끄기");
			}
		}); //스마트 TV 켜기
	}
}

익명 객체의 로컬 변수 사용

익명 객체 내부에서는 외부 클래스의 필드와 메소드를 사용할 수 있다.
하지만 메소드의 매개변수와 로컬변수를 익명 객체에서 사용할 때는 문제가 있다.

what? 😐

메소드 내에서 생성된 익명 객체는 메소드의 실행이 끝나도 힙 메모리에 존재하기 때문에 계속해서 사용할 수 있다.
하지만, 매개변수나 로컬변수는 메소드 실행이 끝나면 스택 메모리에서 사라지기 때문에 익명 객체에서 사용할 수 없게 되므로 문제가 발생한다.

how? 🤫

익명 객체 내부에서 메소드의 매개변수나 로컬변수를 사용할 경우, 이 변수들은 final특성을 가져야 한다.
자바 8 이후부터는 final 키워드를 따로 붙이지 않아도 final특성을 가지고 있다는 것을 기억하자.

// 인터페이스 선언

public interface Calculatable{
	public int sum();
}
public class Anonymous{
    public int field;
    public void method(final int arg1, int arg2){
        final int var1 = 0;
        int var2 = 0; 
                
        field = 10;
        
        // arg1 = 20; (X)
        // var2 = 30; (X)
        
        Calculatable calc = new Calculatable(){
            @Override
            public int sum(){
            	int result = field + arg1 + arg2 + var1 + var2;
                // 익명 객체 내부에서 메소드의 매개변수와 로컬변수를 사용 -> 얘네는 final 특성을 가지고 있음을 기억하기.
                return result;
            }
        };
        System.out.println(clac.sum());
    }
}
public class AnonymousExample{
    public static void main(String[] args) {
   	Anonymous anony = new Anonymous(); // 객체 생성
        anony.method(0,0);  // 출력 결과는 10 + 0 + 0 + 0 + 0 = 10
    }
}

마치면서

익명 객체는 이 정도로 정리할 수 있을 것 같다.
처음에 배울 때에는 그냥 한번 훑고 넘겼는데, 스레드나 다른 이벤트 객체 생성시 자주 사용한다고 하니 공부해두면 좋을 것 같다.

profile
계속해서 기록하는 개발자. 조금씩 성장하기!

0개의 댓글