< 김영한의 실전 자바 - 기본편 > 강의를 보고 이해한 내용을 바탕으로 합니다.
자바는 순수 추상 클래스를 더 편리하게 사용할 수 있는 인터페이스라는 기능을 제공한다.
public abstract class AbstractAnimal {
public abstract void sound();
public abstract void move();
}
인터페이스는 class
가 아니라 interface
키워드를 사용하면 된다.
public interface class InterfaceAnimal {
public abstract void sound();
public abstract void move();
}
인터페이스는 public abstract 키워드 생략 가능
public interface class InterfaceAnimal {
void sound();
void move();
}
순수 추상 클래스의 특징
public
, abstract
이다.public abstract
를 생략할 수 있다. (생략 권장)public static final
)public interface InterfaceAnimal {
void sound(); //public abstract 생략
void move();
}
public class Dog implements InterfaceAnimal{ //implements : 구현하겠략
@Override
public void sound() {
System.out.println("멍멍");
}
@Override
public void move() {
System.out.println("개 이동");
}
}
public class InterfaceMain {
public static void main(String[] args) {
//인터페이스 완전 추상이라 생성 불가
//InterfaceAnimal interfaceAnimal = new InterfaceAnimal();
Cat cat = new Cat();
Dog dog = new Dog();
Cow cow = new Cow();
soundAnimal(cat);
soundAnimal(dog);
soundAnimal(cow);
}
//변하지 않는 부분
private static void soundAnimal(InterfaceAnimal animal){
System.out.println("동물 소리 테스트 시작");
animal.sound();
System.out.println("동물 소리 테스트 종료");
}
}
.class
로 다루어진다. 인터페이스를 작성할 때도 .java
에 인터페이스를 정의한다.public abstract
는 생략되어 있는 애다.부모 클래스의 기능을 자식 클래스가 상속 받을 때 클래스는 상속 받는다고 표현하지만, 부모 인터페이스의 기능을 자식이 상속 받을 때는 인터페이스를 구현한다고 표현한다.
이렇게 서로 다르게 표현하는 이유가 뭘까?
상속은 이름 그대로 부모의 기능을 물려 받는 것이 목적이다. 하지만 인터페이스는 모든 메서드가 추상 메서드이다. 따라서 물려받을 수 있는 기능이 없고 인터페이스에 정의한 모든 메서드를 자식이 오버라이딩 해서 기능을 구현해야 한다. 따라서 구현한다고 표현한다.
인터페이스는 메서드 이름만 있는 설계도이고, 이 설계도가 실제 어떻게 작동하는지는 하위 클래스에서 모두 구현해야한다. 따라서 인터페이스의 경우 상속이 아니라 해당 인터페이스를 구현한다고 표현한다.
(상속과 구현은 사람이 표현하는 단어만 다를 뿐 자바 입장에서는 똑같다. 일반 상속 구조와 동일하게 작동한다.)
모든 메서드가 추상 메서드인 경우 순수 추상 클래스를 만들어도 되고 인터페이스를 만들어도 된다. 그런데 왜 인터페이스를 사용해야 할까?
단순히 편리하다는 이유를 넘어서 다음과 같은 이유가 있다.
클래스는 다중 상속이 안되는데 인터페이스의 다중 구현은 혀용되는 이유가 뭘까?
인터페이스는 모두 추상 메서드로 이루어져 있기 때문이다.
InterfaceA
, InterfaceB
는 둘 다 같은 methodCommon()
을 가지고 있다.
그리고 Child
는 두 인터페이스를 구현했다.
상속 관계의 경우 두 부모 중에 어떤 한 부모의 methodCommon()
을 사용해야 할지 결정해야 하는 다이아몬드 문제가 발생한다.
하지만 인터페이스 자신은 구현을 가지지 않는다. 여기서 InterfaceA
, InterfaceB
는 같은 이름의 methodCommon()
을 제공하지만 이것의 기능은 Child가 구현한다. 그리고 오버라이딩에 의해 어차피 Child에 있는 methodCommon()
이 호출된다. 결과적으로 두 부모 중에 어떤 한 부모의 methodCommon()
을 선택하는 것이 아니라 그냥 인터페이스들을 구현한 Child에 있는 methodCommon()
이 사용된다. 이런 이유로 인터페이스는 다이아몬드 문제가 발생하지 않아 인터페이스의 경우엔 다중 구현을 허용한다.
public class Child implements InterfaceA, InterfaceB{
@Override
public void methodA() {
System.out.println("Child.methodA");
}
@Override
public void methodCommon() {
System.out.println("Child.methodB");
}
@Override
public void methodB() {
System.out.println("Child.methodCommon");
}
}
public class DiamondMain {
public static void main(String[] args) {
//InterfaceA a = new InterfaceA() ; //안됨
InterfaceA a = new Child();
a.methodA();
a.methodCommon();
InterfaceB b = new Child();
b.methodB();
b.methodCommon();
}
}
클래스 상속과 인터페이스 구현을 함께 사용하는 예를 알아보자.
public class Bird extends AbstractAnimal implements Fly {
@Override
public void sound() {
System.out.println("짹짹");
}
@Override
public void fly() {
System.out.println("새 날기");
}
}
extends
를 통한 상속은 하나만 할 수 있고 implements
를 통한 인터페이스는 다중 구현할 수 있기 때문에 둘이 함께 나온 경우에 extends
가 먼저 나와야 한다.
public class SoundFlyMain {
public static void main(String[] args) {
Dog dog = new Dog();
Bird bird = new Bird();
Chicken chicken = new Chicken();
soundAnimal(dog);
soundAnimal(bird);
soundAnimal(chicken);
flyAnimal(bird);
flyAnimal(chicken);
}
//AbstractAnimal 사용 가능
private static void soundAnimal(AbstractAnimal animal){
System.out.println("동물 소리 테스트 시작");
animal.sound();
System.out.println("동물 소리 테스트 종료");
}
//Fly 인터페이스가 있으면 사용 가능
private static void flyAnimal(Fly fly){
System.out.println("날기 테스트 시작");
fly.fly();
System.out.println("날기 테스트 종료");
}
}