인터페이스는 객체의 기능을 정의한 일종의 껍데기로, 추상 메서드만 포함하며 실제 구현 내용은 포함되지 않는다. 인터페이스에 선언된 추상 메서드는 이를 구현한 클래스에서 반드시 구현해야 한다. 이 규칙은 추상 클래스와 동일하다.
static final 상수만 필드로 가질 수 있으며, 추상 메서드와 default 메서드만 포함할 수 있다. 일반 메서드는 포함할 수 없다.인터페이스를 구현한 여러 클래스가 동일한 인터페이스 메서드를 구현하되, 각기 다른 방식으로 동작하도록 할 수 있다. 이는 코드의 유연성을 높인다.
package chap08.payment;
public interface Payment {
void processPayment(double amount);
}
다양한 클래스에서 Payment 인터페이스를 구현할 수 있다.
public class CreditCard implements Payment {
private String cardNumber;
public CreditCard(String cardNumber){
this.cardNumber = cardNumber;
}
@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment of $" + amount + " using card number: " + cardNumber);
}
}
public class Paypal implements Payment {
private String email;
public Paypal(String email) {
this.email = email;
}
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of $" + amount + " using email: " + email);
}
}
public class PaymentProcess {
public static void main(String[] args) {
Payment creditCard = new CreditCard("12345-67890");
creditCard.processPayment(67000);
Payment paypal = new Paypal("email@aws.com");
paypal.processPayment(67000);
}
}
위 예시에서 Payment 인터페이스를 사용해 두 가지 결제 방식(신용카드, 페이팔)을 유연하게 처리할 수 있다. 이는 다형성의 대표적인 예다.
Java 8 이후 인터페이스에 default 메서드와 static 메서드를 포함할 수 있게 되었다.
public interface Vehicle {
default void start() {
System.out.println("Vehicle is starting.");
}
static void stop() {
System.out.println("Vehicle has stopped.");
}
}
| 구분 | 추상 클래스 (abstract class) | 인터페이스 (interface) |
|---|---|---|
| 사용 키워드 | abstract | interface |
| 사용 가능 변수 | 제한 없음 (일반 필드, static final 모두 가능) | static final (상수만 가능) |
| 사용 가능 접근 제어자 | 제한 없음 (public, private, protected, default) | public (모든 메서드는 기본적으로 public) |
| 사용 가능 메서드 | 제한 없음 (추상 메서드, 일반 메서드 모두 가능) | abstract method, default method, static method, private method |
| 상속 키워드 | extends | implements |
| 다중 상속 가능 여부 | 불가능 (단일 상속만 가능) | 가능 (클래스에 다중 구현, 인터페이스끼리 다중 상속 가능) |
| 구현 강제성 | 일부 메서드는 구현하지 않아도 됨 | 모든 추상 메서드는 구현 클래스에서 필수 |
| 유연성 | 특정 클래스 계층 구조에서만 사용 가능 | 여러 클래스에서 동일 기능을 제공 가능 |
| 공통점 | 1. 추상 메서드를 가질 수 있음
2. 인스턴스화 불가능 (new 생성자 사용 불가)
3. 추상 클래스나 인터페이스를 구현한 인스턴스를 사용해야 함
4. 추상 메서드를 상속받은 클래스는 반드시 구현해야 함 |
| --- | --- |
새로운 기능을 추가할 때, 추상 클래스와 인터페이스의 차이는 크게 유연성과 다중 상속 여부에서 차이가 난다.
abstract class Payment를 사용한다면, BitCoin은 오직 Payment만 상속받을 수 있으며, 다른 클래스를 상속받는 것이 불가능하다.abstract class Payment {}
public class BitCoin extends Payment {}
abstract class Virtual {} // 다중 상속 불가 (X)interface Payment {}
interface Virtual {}
public class BitCoin implements Payment, Virtual {} // 다중 구현 가능 (O)Is a 관계, 즉 상속관계를 표현하는 데 사용된다.예: C is A -> C는 A에 포함된다.Has a 관계, 즉 특정 기능이나 능력을 표현하는 데 사용된다.예: C has a I -> C는 I의 기능을 갖는다.따라서 인터페이스의 유연한 확장성이 큰 장점이다. 여러 클래스가 같은 기능을 공통으로 사용할 수 있게 해주는 것이 인터페이스의 주요 목적이다.
기존의 Zookeeper 클래스에서는 각각의 동물마다 별도의 메서드를 만들었지만, 인터페이스를 사용하면 하나의 메서드로 여러 객체를 받을 수 있어 중복 코드를 줄일 수 있다.
public class Zookeeper {
void feed(Lion lion){
System.out.println("feed fish");
}
void feed(Tiger tiger){
System.out.println("feed meat");
}
}
인터페이스를 적용하여 아래처럼 타입을 통일할 수 있다.
public class Zookeeper {
void feed(Predator predator){
System.out.println("feed " + predator.getFood());
}
}
Predator 인터페이스를 사용하면 Lion, Tiger와 같은 동물들이 각각 다른 음식을 받을 수 있다.public interface Predator {
String getFood();
}
public class Lion implements Predator {
@Override
public String getFood() {
return "fish";
}
}
public class Tiger implements Predator {
@Override
public String getFood() {
return "meat";
}
}
결과: Zookeeper 클래스는 어떤 동물이 들어오더라도 동일한 방식으로 처리할 수 있게 된다.
인터페이스로 구현 객체를 사용할 때, 인터페이스 타입으로 변수를 선언하고, 구현 객체를 대입할 수 있다.
Predator predator = new Lion();
이렇게 하면 Lion이든 Tiger이든 하나의 타입으로 처리할 수 있어 코드가 유연해지고, 확장 가능해진다.
인터페이스의 다형성은 하나의 인터페이스 타입에 여러 객체를 대입할 수 있게 해준다. 이렇게 하면 객체의 타입에 따라 다른 동작을 할 수 있다.
public interface Predator {
default void hunt() {
System.out.println("Default hunting method");
}
}
인터페이스에서는 인스턴스 변수를 사용할 수 없고, 생성자도 가질 수 없다. 하지만 정적 메서드, 디폴트 메서드, private 메서드는 사용할 수 있다.