[Java 개념노트 35] 인터페이스 이해하기
안녕하세요. Java 개념노트 시리즈 서른다섯 번째 글입니다.
지난 글에서는 공통 기능은 제공하면서 일부 기능은 자식 클래스가 반드시 구현하도록 만드는 추상 클래스에 대해 정리했습니다. 이번 글에서는 자바 객체지향에서 매우 중요한 개념인 인터페이스에 대해 알아보겠습니다.
인터페이스는 처음에는 추상 클래스와 비슷하게 느껴질 수 있습니다. 하지만 인터페이스는 클래스가 반드시 지켜야 할 기능 약속을 정의하는 데 더 가깝습니다.
1. 인터페이스란?
인터페이스는 클래스가 반드시 구현해야 할 기능 목록을 정의하는 문법입니다.
인터페이스 안에는 주로 메서드의 이름과 형태만 작성합니다. 실제 동작은 인터페이스를 구현하는 클래스에서 작성합니다.
쉽게 말하면
인터페이스는 “이 기능들은 반드시 만들어야 한다”라고 약속하는 기능 명세서입니다.
2. interface 키워드
인터페이스를 만들 때는 interface 키워드를 사용합니다.
interface RemoteControl {
void turnOn();
void turnOff();
}
위 코드는 RemoteControl이라는 인터페이스입니다. 이 인터페이스를 구현하는 클래스는 turnOn()과 turnOff() 메서드를 반드시 구현해야 합니다.
3. 인터페이스 메서드의 특징
인터페이스의 추상 메서드는 기본적으로 public abstract 성격을 가집니다. 따라서 아래 두 코드는 비슷한 의미로 볼 수 있습니다.
interface RemoteControl {
void turnOn();
}
interface RemoteControl {
public abstract void turnOn();
}
보통은 짧게 void turnOn();처럼 작성합니다. 인터페이스 안의 추상 메서드는 구현부가 없기 때문에 중괄호 { }를 작성하지 않고 세미콜론 ;으로 끝납니다.
주의
인터페이스의 추상 메서드는 구현부가 없습니다. 실제 코드는 인터페이스를 구현하는 클래스에서 작성합니다.
4. implements 키워드
클래스가 인터페이스를 구현할 때는 implements 키워드를 사용합니다.
class 클래스이름 implements 인터페이스이름 {
인터페이스 메서드 구현
}
예를 들어 Television 클래스가 RemoteControl 인터페이스를 구현한다면 다음과 같이 작성합니다.
class Television implements RemoteControl {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
}
Television 클래스는 RemoteControl 인터페이스를 구현했기 때문에 turnOn(), turnOff() 메서드를 반드시 작성해야 합니다.
5. 인터페이스 기본 예제
인터페이스를 사용하는 전체 예제를 보겠습니다.
public class Main {
public static void main(String[] args) {
Television tv = new Television();
tv.turnOn();
tv.turnOff();
}
}
interface RemoteControl {
void turnOn();
void turnOff();
}
class Television implements RemoteControl {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
}
실행 결과는 다음과 같습니다.
TV를 켭니다.
TV를 끕니다.
RemoteControl 인터페이스는 켜기와 끄기 기능을 약속합니다. Television 클래스는 그 약속을 지키기 위해 두 메서드를 구현했습니다.
6. 인터페이스 메서드는 public으로 구현해야 한다
인터페이스의 추상 메서드는 기본적으로 public입니다. 따라서 구현 클래스에서 메서드를 작성할 때도 public으로 작성해야 합니다.
올바른 예
class Television implements RemoteControl {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
}
잘못된 예
class Television implements RemoteControl {
@Override
void turnOn() {
System.out.println("TV를 켭니다.");
}
}
위 코드는 오류가 발생할 수 있습니다. 인터페이스의 메서드는 public인데, 구현 메서드의 접근 범위를 더 좁게 만들었기 때문입니다.
핵심 포인트
인터페이스 메서드를 구현할 때는 보통 public을 붙여야 합니다.
7. 인터페이스는 객체를 직접 만들 수 없다
인터페이스는 추상 클래스처럼 직접 객체를 만들 수 없습니다.
RemoteControl remote = new RemoteControl(); // 오류 발생
인터페이스는 기능 약속만 정의한 것이므로 직접 객체로 만들 수 없습니다. 대신 인터페이스를 구현한 클래스의 객체를 만들 수 있습니다.
RemoteControl remote = new Television();
위 코드는 가능합니다. 인터페이스 타입 변수로 구현 클래스 객체를 참조하는 것입니다. 이 구조는 다형성과 연결됩니다.
8. 인터페이스와 다형성
인터페이스도 타입으로 사용할 수 있습니다. 즉, 인터페이스 타입 변수로 그 인터페이스를 구현한 객체를 참조할 수 있습니다.
public class Main {
public static void main(String[] args) {
RemoteControl remote1 = new Television();
RemoteControl remote2 = new Speaker();
remote1.turnOn();
remote2.turnOn();
}
}
interface RemoteControl {
void turnOn();
void turnOff();
}
class Television implements RemoteControl {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
}
class Speaker implements RemoteControl {
@Override
public void turnOn() {
System.out.println("스피커를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("스피커를 끕니다.");
}
}
실행 결과는 다음과 같습니다.
TV를 켭니다.
스피커를 켭니다.
변수 타입은 모두 RemoteControl입니다. 하지만 실제 객체가 Television인지 Speaker인지에 따라 다른 동작이 실행됩니다.
9. 인터페이스 배열 활용하기
인터페이스 타입 배열을 사용하면 서로 다른 구현 클래스 객체를 하나의 배열로 다룰 수 있습니다.
public class Main {
public static void main(String[] args) {
RemoteControl[] remotes = {
new Television(),
new Speaker()
};
for (int i = 0; i < remotes.length; i++) {
remotes[i].turnOn();
remotes[i].turnOff();
}
}
}
interface RemoteControl {
void turnOn();
void turnOff();
}
class Television implements RemoteControl {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
}
class Speaker implements RemoteControl {
@Override
public void turnOn() {
System.out.println("스피커를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("스피커를 끕니다.");
}
}
실행 결과는 다음과 같습니다.
TV를 켭니다.
TV를 끕니다.
스피커를 켭니다.
스피커를 끕니다.
배열 타입은 RemoteControl[] 하나이지만, 배열 안에는 Television과 Speaker 객체를 함께 넣을 수 있습니다.
10. 인터페이스는 여러 개 구현할 수 있다
자바 클래스는 부모 클래스를 하나만 상속받을 수 있습니다. 하지만 인터페이스는 여러 개 구현할 수 있습니다.
class 클래스이름 implements 인터페이스1, 인터페이스2 {
메서드 구현
}
예를 들어 스마트폰은 전화를 걸 수도 있고, 음악을 재생할 수도 있습니다. 이 기능을 각각 인터페이스로 나눌 수 있습니다.
public class Main {
public static void main(String[] args) {
SmartPhone phone = new SmartPhone();
phone.call();
phone.playMusic();
}
}
interface Callable {
void call();
}
interface MusicPlayer {
void playMusic();
}
class SmartPhone implements Callable, MusicPlayer {
@Override
public void call() {
System.out.println("전화를 겁니다.");
}
@Override
public void playMusic() {
System.out.println("음악을 재생합니다.");
}
}
실행 결과는 다음과 같습니다.
전화를 겁니다.
음악을 재생합니다.
SmartPhone 클래스는 Callable과 MusicPlayer 두 인터페이스를 구현했습니다. 따라서 두 인터페이스의 메서드를 모두 작성해야 합니다.
11. 인터페이스 상수
인터페이스에 선언한 필드는 기본적으로 public static final 성격을 가집니다. 즉, 인터페이스의 필드는 상수처럼 사용됩니다.
interface AppConfig {
int MAX_COUNT = 100;
}
위 코드는 다음과 비슷한 의미입니다.
interface AppConfig {
public static final int MAX_COUNT = 100;
}
인터페이스 상수는 다음처럼 사용할 수 있습니다.
System.out.println(AppConfig.MAX_COUNT);
12. default 메서드
인터페이스에는 구현부가 있는 default 메서드를 작성할 수도 있습니다.
interface RemoteControl {
void turnOn();
default void checkBattery() {
System.out.println("배터리를 확인합니다.");
}
}
checkBattery()는 default 메서드이므로 인터페이스 안에 구현부를 가질 수 있습니다. 구현 클래스는 이 메서드를 그대로 사용할 수도 있고, 필요하면 오버라이딩할 수도 있습니다.
class Television implements RemoteControl {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
}
사용 예시는 다음과 같습니다.
Television tv = new Television();
tv.turnOn();
tv.checkBattery();
13. static 메서드
인터페이스에는 static 메서드도 작성할 수 있습니다. static 메서드는 인터페이스 이름으로 호출합니다.
interface Calculator {
static int add(int a, int b) {
return a + b;
}
}
호출은 다음과 같이 합니다.
int result = Calculator.add(10, 20);
System.out.println(result);
실행 결과는 다음과 같습니다.
30
기억하기
인터페이스의 static 메서드는 구현 객체가 아니라 인터페이스 이름으로 호출합니다.
14. 인터페이스와 추상 클래스 비교
인터페이스와 추상 클래스는 비슷해 보이지만 사용 목적이 다릅니다.
| 구분 | 추상 클래스 | 인터페이스 |
| 핵심 느낌 | 상속용 미완성 부모 클래스 | 기능 약속, 기능 명세서 |
| 키워드 | abstract class |
interface |
| 사용 방식 | extends |
implements |
| 다중 사용 | 클래스 상속은 하나만 가능 | 여러 인터페이스 구현 가능 |
| 목적 | 공통 상태와 기능 제공 | 반드시 구현할 기능 약속 |
15. 인터페이스는 언제 사용할까?
인터페이스는 클래스들이 같은 기능을 반드시 가지도록 약속하고 싶을 때 사용합니다.
예를 들어 결제 기능은 카드 결제, 현금 결제, 간편 결제 등 여러 방식이 있을 수 있습니다. 방식은 다르지만 모두 pay() 기능을 가져야 한다면 인터페이스로 약속할 수 있습니다.
interface Payment {
void pay(int amount);
}
이제 결제 클래스들은 모두 pay() 메서드를 구현해야 합니다.
16. 인터페이스 전체 예제: 결제 기능
결제 기능을 인터페이스로 만들어보겠습니다.
public class Main {
public static void main(String[] args) {
Payment cardPayment = new CardPayment();
Payment cashPayment = new CashPayment();
cardPayment.pay(30000);
cashPayment.pay(15000);
}
}
interface Payment {
void pay(int amount);
}
class CardPayment implements Payment {
@Override
public void pay(int amount) {
System.out.println(amount + "원을 카드로 결제합니다.");
}
}
class CashPayment implements Payment {
@Override
public void pay(int amount) {
System.out.println(amount + "원을 현금으로 결제합니다.");
}
}
실행 결과는 다음과 같습니다.
30000원을 카드로 결제합니다.
15000원을 현금으로 결제합니다.
변수 타입은 모두 Payment입니다. 하지만 실제 객체가 CardPayment인지 CashPayment인지에 따라 결제 방식이 다르게 실행됩니다.
17. 인터페이스와 instanceof
instanceof는 클래스뿐만 아니라 인터페이스 타입 확인에도 사용할 수 있습니다.
Payment payment = new CardPayment();
System.out.println(payment instanceof Payment);
System.out.println(payment instanceof CardPayment);
실행 결과는 다음과 같습니다.
true
true
CardPayment 객체는 Payment 인터페이스를 구현했기 때문에 payment instanceof Payment 결과가 true입니다.
18. 인터페이스 사용 시 자주 하는 실수
인터페이스를 처음 배울 때는 아래와 같은 실수를 자주 합니다.
| 실수 | 문제점 | 해결 방법 |
| 인터페이스를 직접 생성 | new Interface()는 불가능 |
구현 클래스 객체를 생성 |
구현 메서드에 public 누락 |
접근 범위가 좁아져 오류 발생 | 구현 메서드는 public으로 작성 |
| 필수 메서드 구현 누락 | 구현 클래스가 완성되지 않음 | 인터페이스의 모든 추상 메서드 구현 |
extends와 implements 혼동 |
문법 오류 발생 | 클래스 상속은 extends, 인터페이스 구현은 implements |
| 인터페이스와 추상 클래스를 같은 것으로 생각 | 설계 목적을 혼동 | 인터페이스는 기능 약속에 가깝다고 이해 |
19. 직접 연습해보기
아래 코드를 직접 작성하고 실행해보세요.
public class Main {
public static void main(String[] args) {
Notifier email = new EmailNotifier();
Notifier sms = new SmsNotifier();
email.send("회원가입을 환영합니다.");
sms.send("인증번호는 1234입니다.");
Notifier[] notifiers = {
new EmailNotifier(),
new SmsNotifier()
};
for (int i = 0; i < notifiers.length; i++) {
notifiers[i].send("공지 메시지입니다.");
}
}
}
interface Notifier {
void send(String message);
}
class EmailNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("이메일 발송: " + message);
}
}
class SmsNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("문자 발송: " + message);
}
}
실행 결과는 다음과 같습니다.
이메일 발송: 회원가입을 환영합니다.
문자 발송: 인증번호는 1234입니다.
이메일 발송: 공지 메시지입니다.
문자 발송: 공지 메시지입니다.
20. 이번 글 정리
이번 글에서는 클래스가 반드시 지켜야 할 기능 약속을 정의하는 인터페이스에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.
- 인터페이스는 클래스가 반드시 구현해야 할 기능 목록을 정의한다.
- 인터페이스는
interface키워드로 만든다. - 클래스가 인터페이스를 구현할 때는
implements를 사용한다. - 인터페이스의 추상 메서드는 기본적으로
public abstract성격을 가진다. - 구현 클래스의 메서드는 보통
public으로 작성해야 한다. - 인터페이스는 직접 객체를 생성할 수 없다.
- 인터페이스 타입 변수로 구현 클래스 객체를 참조할 수 있다.
- 인터페이스도 다형성과 함께 사용할 수 있다.
- 클래스는 여러 인터페이스를 구현할 수 있다.
- 인터페이스의 필드는 기본적으로
public static final상수 성격을 가진다. - 인터페이스에는
default메서드와static메서드도 작성할 수 있다.
한 줄 요약
인터페이스는 클래스가 반드시 구현해야 할 기능을 약속하고, 여러 구현 클래스를 하나의 타입으로 다룰 수 있게 해주는 객체지향 문법입니다.
다음 글 예고
다음 글에서는 [Java 개념노트 36] 추상 클래스와 인터페이스 비교하기라는 주제로 두 개념이 어떤 점에서 비슷하고 어떻게 다른지 정리해보겠습니다.
GWDEVELBlog Java 개념노트 시리즈

'Computer Science > Java' 카테고리의 다른 글
| [Java ] 개념노트 37 Object 클래스 이해하기 (0) | 2026.06.14 |
|---|---|
| [Java] 개념노트 36 추상 클래스와 인터페이스 비교하기 (1) | 2026.06.14 |
| [Java]개념노트 34 추상 클래스 이해하기 (0) | 2026.06.13 |
| [Java] 개념노트 33 참조변수의 형변환과 instanceof 이해하기 (0) | 2026.06.13 |
| [Java] 개념노트 33 참조변수의 형변환과 instanceof 이해하기 (0) | 2026.06.13 |