인터페이스는 인터페이스를 구현하는 모든 클래스에 대해 특정한 메소드가 반드시 존재하도록 강제한다. 인터페이스의 목적은 구현 객체가 같은 동작을 한다는 것을 보장하는 것이다. 일종의 추상 클래스지만 추상 클래스보다 추상화 정도가 높아서 추상 메소드 이외의 일반 메소드나 멤버 변수를 구성원으로 가질 수 없다. 오직 추상 메소드와 상수만 멤버로 가질 수 있으며, 그 외의 요소는 허용하지 않는다. 추상 클래스를 부분적으로만 완성된 미완성 설계도라고 한다면 인터페이스는 구현된 것이 아무것도 없는 기본 설계도라 할 수 있다.
public static final
이어야 하며, 생략 가능하다.public abstract
이어야 하며, 생략 가능하다.static
메소드와 default
메소드의 추가를 허용했다.interface PlayCard {
// 상수 선언 (모두 public static final)
int NUMBER = 4;
int DIAMOND = 3;
int HEART = 2;
int CLOVER = 1;
// 추상 메소드 선언 (모두 public abstract)
int getCardNumber();
String getCardKind();
// default 메소드 (JDK 1.8부터 지원)
default void getNewCard() {
System.out.println("새로운 카드를 받았습니다.");
}
// static 메소드 (JDK 1.8부터 지원)
static void staticMethod() {
System.out.println("스태틱 메소드입니다.");
}
}
인터페이스는 인터페이스로부터만 상속(extends) 받을 수 있다. 클래스와는 달리 다중 상속, 즉 여러 개의 인터페이스로부터 상속 받는 것이 가능하다. 또한 클래스에서 여러 인터페이스를 다중 구현하는 것도 가능하다.
interface Movable {
void move(int x, int y);
}
interface Attackable {
void attack(Unit u);
}
interface Fightable extends Movable, Attackable {
// Fightable 인터페이스는 Movable과 Attackable을 상속받음
}
추상 클래스와 마찬가지로 인터페이스 자체로는 인스턴스를 생성할 수 없다. 따라서 인터페이스의 추상 메소드를 구현해 줄 클래스를 작성해야 한다. 상속은 extends 키워드를 사용하며, 인터페이스는 implements 키워드를 사용한다.
abstract class Fighter implements Fightable {
// move() 메소드를 구현
public void move(int x, int y) {
System.out.println("전진합니다.");
}
// attack() 메소드는 구현하지 않음
}
class Fighter extends Unit implements Fightable {
// move() 메소드 구현
public void move(int x, int y) {
System.out.println("전진합니다.");
}
// attack() 메소드 구현
public void attack(Unit u) {
System.out.println("공격합니다.");
}
}
다중 상속을 허용할 경우, 메소드 출처의 모호성이 발생할 수 있다. 예를 들어:
class Animal {
public void cry() {
System.out.println("짖기!");
}
}
class Cat extends Animal {
@Override
public void cry() {
System.out.println("냐옹냐옹!");
}
}
class Dog extends Animal {
@Override
public void cry() {
System.out.println("멍멍!");
}
}
// 다중 상속을 허용할 경우 모호성이 발생
class MyPet extends Cat, Dog { }
public class Test {
public static void main(String[] args) {
MyPet pet = new MyPet();
pet.cry(); // 어떤 메소드를 호출할지 모호하다
}
}
이와 같은 이유로 자바에서는 다중 상속을 지원하지 않는다. 하지만, 인터페이스를 이용해 다중 구현을 하면 메소드 호출의 모호성을 방지할 수 있다.
interface Animal {
void cry();
}
interface Cat extends Animal {
void cry();
}
interface Dog extends Animal {
void cry();
}
class Pet implements Cat, Dog {
@Override
public void cry() {
System.out.println("멍멍~ 냐옹냐옹~");
}
}
public class Test {
public static void main(String[] args) {
Pet pet = new Pet();
pet.cry(); // 모호성 없이 Pet 클래스의 cry() 메소드가 호출된다
}
}
// 결과: 멍멍~ 냐옹냐옹~
인터페이스도 인터페이스를 구현한 클래스의 조상이라고 할 수 있으므로, 해당 인터페이스 타입의 참조 변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있다.
interface Animal {
void sound();
}
class Dog implements Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
}
class Cat implements Animal {
@Override
public void sound() {
System.out.println("냐옹");
}
}
public class Test {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.sound(); // 멍멍
cat.sound(); // 냐옹
}
}
인터페이스는 자바에서 매우 중요한 개념으로, 클래스가 구현해야 할 메소드를 강제함으로써 일관된 동작을 보장한다. 이를 통해 코드의 유연성과 확장성을 높일 수 있으며, 다중 상속의 문제를 해결하는 데에도 유용하다. 따라서, 인터페이스를 적절하게 활용하면 보다 유지보수성이 높고 확장 가능한 코드를 작성할 수 있다.