1. 인터페이스란?
- 정의(내 말로)
인터페이스는 “클래스가 반드시 제공해야 할 메서드 목록(역할)을 정의해 둔 설계도”. - 특징
- 메서드의 이름/파라미터/리턴 타입(시그니처)만 정의하고, 실제 구현은 없다. (단, Java 8+부터는
default/static메서드, Java 9+부터는private메서드로 인터페이스 안에 구현을 둘 수도 있다.) - 구현은
implements하는 클래스에서 담당한다. - 공통된 “역할”만 약속하고, “어떻게 동작하는지”는 각 클래스가 다르게 구현할 수 있게 해준다.
- 메서드의 이름/파라미터/리턴 타입(시그니처)만 정의하고, 실제 구현은 없다. (단, Java 8+부터는
예: Animal 인터페이스
1
2
3
public interface Animal {
void sound();
}
구현 클래스들:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Dog implements Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
}
public class Cat implements Animal {
@Override
public void sound() {
System.out.println("야옹");
}
}
사용:
1
2
3
4
5
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.sound(); // 멍멍
a2.sound(); // 야옹
포인트:
- 변수 타입은
Animal하나로 통일할 수 있다. - 실제 동작은
Dog,Cat구현에 따라 달라진다. - 인터페이스 덕분에 “역할(Animal)” 기준으로 코드를 작성할 수 있다.
2. 함수형 인터페이스
- 정의(내 말로)
“추상 메서드가 딱 1개만 있는 인터페이스”. - 이렇게 메서드가 하나뿐이면, 이 인터페이스는 “함수 하나”처럼 다룰 수 있다.
- 주로
@FunctionalInterface애노테이션을 붙여서 의도/제약을 명시한다.
예:
1
2
3
4
@FunctionalInterface
public interface MyFunction {
int apply(int x, int y);
}
여기서 apply(int x, int y) 메서드 1개만 존재 → 함수형 인터페이스.
3. 구현 클래스 / 익명 클래스 / 람다 비교
같은 함수형 인터페이스를 세 가지 방식으로 구현해보기.
3-1. 구현 클래스로 구현
1
2
3
4
5
6
public class AddFunction implements MyFunction {
@Override
public int apply(int x, int y) {
return x + y;
}
}
사용:
1
2
MyFunction add = new AddFunction();
int result = add.apply(3, 5); // 8
3-2. 익명 클래스로 구현
1
2
3
4
5
6
7
8
MyFunction add = new MyFunction() {
@Override
public int apply(int x, int y) {
return x + y;
}
};
int result = add.apply(3, 5);
- 일회성 구현을 위해 클래스를 따로 만들지 않고, 즉석에서 구현.
3-3. 람다로 구현
1
2
3
MyFunction add = (x, y) -> x + y;
int result = add.apply(3, 5);
(x, y): 파라미터->: 람다 표시x + y: 반환할 식(메서드 몸체)
중요 포인트
위 세 가지 방식은 전부 결국 같은 의미:
- 모두
MyFunction인터페이스를 구현하는 방법이다. - 람다는 “함수형 인터페이스 구현”을 매우 간단한 문법으로 줄여 쓴 것.
4. 람다를 쓸 수 있는 조건
- 모든 인터페이스에 람다를 쓸 수 있는 건 아니다.
- “추상 메서드가 딱 1개인 인터페이스(= 함수형 인터페이스)”에만 람다를 쓸 수 있다.
- 보통
@FunctionalInterface가 붙어 있다.- 예:
Runnable,Comparator<T>,java.util.function.*계열 등
- 예:
5. 스프링/실무에서의 예 – 이벤트 리스너
스프링에서 자주 보게 될 인터페이스: ApplicationListener<E>
(단순화된 형태)
1
2
3
public interface ApplicationListener<E extends ApplicationEvent> {
void onApplicationEvent(E event);
}
- 추상 메서드
onApplicationEvent하나 → 함수형 인터페이스처럼 쓸 수 있다.
5-1. 익명 클래스로 구현
1
2
3
4
5
6
ApplicationListener<MyEvent> listener = new ApplicationListener<MyEvent>() {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("이벤트 발생: " + event);
}
};
5-2. 람다로 구현
1
2
ApplicationListener<MyEvent> listener =
event -> System.out.println("이벤트 발생: " + event);
여기서 책의 문장:
“이들은 함수이므로 람다로 구현할 수 있다”
의 의미:
ApplicationListener같은 인터페이스들은 추상 메서드 1개를 가지는 함수형 인터페이스이다.- 따라서, 그 인터페이스 타입의 객체를 만들 때 람다로 구현할 수 있다는 뜻.
6. 오늘 헷갈렸던 포인트 정리
- 처음에는 “이들은 함수이므로 람다로 구현할 수 있다”라는 말이 이해가 안 됐다.
- 실제 의미:
- “이 인터페이스들은 함수형 인터페이스라서,
굳이 익명 클래스 안 쓰고, 람다 문법으로 바로 구현해도 된다”는 말.
- “이 인터페이스들은 함수형 인터페이스라서,
- 핵심 개념:
- 인터페이스 = 역할(메서드 목록) 설계도
- 함수형 인터페이스 = 추상 메서드 1개
- 함수형 인터페이스는 익명 클래스 대신 람다로 구현 가능
이 세 가지를 연결해서 이해하면,
스프링 책에서 인터페이스/리스너/콜백 관련 설명을 볼 때 훨씬 덜 헷갈릴 것 같다.