멀티 스레드에 대해 공부를 하던 중, Runnable 익명 객체를 매개값으로 사용하면 코드를 절약할 수 있다는 말을 보았다. 익명 객체에 대해 대충은 알고 있었지만 까먹었던 부분도 있고, 정리가 잘 되어있지 않은 것 같아 다시 공부를 하기로 결정했다.
<이것이 자바다> 책을 많이 참고하였다.
익명 객체는 이름이 없는 객체를 말한다.
1번만 사용
하려고 할 때 쓴다.cf) 부모 타입으로 필드/변수를 선언하고, 자식 객체를 초기값으로 대입한다고 하자.
이때 해야할 일은
이다. 아래의 코드를 참고하자.
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는 사용할 수 없다.
이번에는 인터페이스 타입으로 필드와 변수를 선언하고, 구현 객체를 초기값으로 대입하는 경우를 생각해보자. 이때 할 일은
이다.
익명 자식 객체 생성 때와 마찬가지로, 구현 클래스가 재사용 되지 않고 해당 필드 / 변수의 초기값으로만 사용하는 경우라면 익명 구현 객체를 초기값으로 대입하는 것이 좋다.
주의할 점은, 중괄호 {} 안에는 인터페이스에 선언된 모든 추상 메소드의 실체 메소드를 작성해야 한다. 그렇지 않으면 컴파일 에러가 발생한다.
아래의 코드를 참고하자.
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 켜기
}
}
익명 객체 내부에서는 외부 클래스의 필드와 메소드를 사용할 수 있다.
하지만 메소드의 매개변수와 로컬변수를 익명 객체에서 사용할 때는 문제가 있다.
메소드 내에서 생성된 익명 객체는 메소드의 실행이 끝나도 힙 메모리에 존재하기 때문에 계속해서 사용할 수 있다.
하지만, 매개변수나 로컬변수는 메소드 실행이 끝나면 스택 메모리에서 사라지기 때문에 익명 객체에서 사용할 수 없게 되므로 문제가 발생한다.
익명 객체 내부에서 메소드의 매개변수나 로컬변수를 사용할 경우, 이 변수들은 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
}
}
익명 객체는 이 정도로 정리할 수 있을 것 같다.
처음에 배울 때에는 그냥 한번 훑고 넘겼는데, 스레드나 다른 이벤트 객체 생성시 자주 사용한다고 하니 공부해두면 좋을 것 같다.