익명 클래스
- 이름이 없는 클래스, 보통 일시적으로 한번만 사용되고 버려지는 객체이다.
- 따로 클래스 정의 없이 메서드 내에서 바로 클래스를 생성해 인스턴스화 할 수 있으며, 단 한 번만 사용되고 버려진다.
- 부모 클래스의 자원을 상속받아 재정의하여 사용할 자식 클래스가 한번만 사용되고 버려질 상황이라면, 익명 클래스를 사용하는게 효과적이다.
// 부모 클래스
class Animal {
public String bark() {
return "동물이 웁니다";
}
}
public class Main {
public static void main(String[] args) {
// 익명 클래스 : 클래스 정의와 객체화를 동시에. 일회성으로 사용
Animal dog = new Animal() {
@Override
public String bark() {
return "개가 짖습니다";
}
}; // 단 익명 클래스는 끝에 세미콜론을 반드시 붙여 주어야 한다.
// 익명 클래스 객체 사용
dog.bark();
}
}
유의점
- 기존 부모 클래스를 상속한 자식 클래스의 경우, 부모 클래스에 없는 새로운 메서드를 만들어 사용할 수 있다.
- But, 익명 클래스 방식으로 선언하면, 오버라이딩 한 메서드만 사용 가능하고 새로 정의한 메서드는 외부에서 사용이 불가능하다.
// 부모 클래스
class Animal {
public String bark() {
return "동물이 웁니다";
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Animal() {
// @Override 메소드
public String bark() {
return "개가 짖습니다";
}
// 새로 정의한 메소드
public String run() {
return "달리기 ㄱㄱ싱";
}
};
dog.bark();
dog.run(); // ! Error - 외부에서 호출 불가능
}
}
new Animal()
를 통해 생성하는 인스턴스는 별도의 클래스가 아닌, Animal 클래스를 상속받는 익명 클래스이기 때문이다. 따라서 Animal에 없는 run() 메서드는 외부에서 호출이 불가능하고 익명 클래스 내에서만 호출할 수 있다.
인터페이스 익명 구현 객체
- 인터페이스를 일회용으로 구현하여 사용할 필요가 있을 때, 익명 구현 객체로 선언해서 사용
// 인터페이스
interface IAnimal {
public String bark(); // 추상 메소드
public String run();
}
public class Main {
public static void main(String[] args) {
// 인터페이스 익명 구현 객체 생성
IAnimal dog = new IAnimal() {
@Override
public String bark() {
return "개가 짖습니다";
}
@Override
public String run() {
return "개가 달립니다";
}
};
// 인터페이스 구현 객체 사용
dog.bark();
dog.run();
}
}
- 본래 클래스가 인터페이스 구현 후 객체를 만들어야 하지만, 바로 클래스명 없이 객체를 만들 수 있다.
- 이를 익명 구현 객체라고 부른다.
- Thread 생성 시 Runnable도 이런 방식을 사용한다.
- 한계점
- 오직 하나의 인터페이스만 구현하여 객체를 생성할 수 있다.
익명 객체와 람다 표현식, 함수형 인터페이스
- 익명 클래스 기법을 java8의 람다식 문법과 같이 쓰면 매우 효과적이다.
@FunctionalInterface // -> 추상 메서드가 하나인지 검사하여, 함수형 인터페이스가 맞는지 check
public interface Operate {
int operate(int a, int b);
// 인터페이스의 모든 메소드는 예외없이 public이면서 abstact이기에 생략 가능
// default 메서드는 추상 메서드에 포함되지 않는다
default void print() {
System.out.println("출력");
}
}
Operate operate = new Operate() {
public int operate(int a, int b) {
return a + b;
}
};
// 람다식으로 줄이기
Operate operate = (a, b) -> {
return a + b;
};
// 더 짧게 줄이기 (리턴 코드만 있다면 생략이 가능)
Operate operate = (a, b) -> a + b;
- 이러한 람다식 표현의 익명 구현 객체는 2가지 제약이 있다.
- 인터페이스로만 만들 수 있다.
- 하나의 추상 메서드만 선언되어 있는 인터페이스만 가능하다. → 함수형 인터페이스라고 한다.
- 두 개 이상의 추상 메서드가 정의되어 있는 인터페이스의 경우 사용 불가능
람다식의 타입 추론
public ContextV1(Strategy strategy) {
this.strategy = strategy;
}
ContextV1 contextV1 = new ContextV1( () -> log.info("비즈니스 로직 1 실행"));
contextV1.execute();
- ContextV1는 Strategy 타입을 받아야 한다. 그런데, 람다식에서는 Strategy 타입에 대한 명시가 없다.
- 하지만 이 코드는 동작한다. 컴파일러 스스로 람다 함수식을 보고 추론하여 타입을 유추하기 때문이다.
- 대부분 함수형 인터페이스는 제네릭을 사용하고, 컴파일러는 대부분 제네릭에서 해당 타입 정보를 판별한다.
Share article