강좌 Course 1. Part 4. ch5 요약
다형성을 보장한다는 것은 부모가 명령을 내리면 자식이 반드시 동작해야 한다는 의미이다. 다형성을 보장하려면 다형성 전제조건 4가지가 충족되어야 하고, 동작의 재정의는 필수이며, 부모클래스가 추상클래스여야 한다.
재정의를 하지 않은 경우, 실행은 되지만 다형성이 보장되지 않는다. 다형성을 보장하려면 재정의가 강제되어야 한다.
여기서 등장하는 것이 추상클래스, 인터페이스이다.
메서드는 머리와 구현부(=헤드와 바디)로 이루어져 있는데, 머리만 있고 구현부가 없는 불완전한 메서드를 추상메서드라고 한다. (+머리에 abstract라는 키워드가 추가된다.)
이러한 추상메서드를 가진 클래스는 마찬가지로 abstract라는 키워드를 가지고 추상클래스가 된다.
public abstract class Animal{
public abstract void ear(){}
}
추상클래스는 불완전한 클래스기 때문에 단독으로 객체를 생성할 수 없고, 반드시 자식클래스가 재정의를 통해 완성해야 한다. 자식 클래스에서 동작을 재정의하지 않을 경우 오류가 발생한다. 추가로, 자식의 역할은 수행할 수 없다.
추상메서드는 재정의를 유도함으로써 다형성을 보장하기 위해 등장하였다. 서로 비슷한 클래스의 공통부분을 묶을 때 사용하며, 구현부가 없는 추상메서드 뿐만 아니라 구현부가 있는 메서드도 가질 수 있다.
// 예시 추상클래스
public abstract class Animal{
public abstract void eat();
public void move(){
System.out.println("They move in herds");
}
}
public class AbstractClassTest {
public static void main(String[] args) {
// Animal ani = new Animal(); // 단독으로 객체 생성 x
Animal ani = new Dog();
ani.eat(); // Eat like a dog
ani.move();
ani = new Cat();
ani.eat(); // Eat like a cat
ani.move();
((Cat)ani).night();
}
}
한꺼번에 재정의를 할 때에는 재정의를 하라고 extends 가 들어간 라인의 빨간줄을 우클릭-implement methods에 들어가서 재정의할 메서드들을 선택하면 구현부만 작성하면 되는 코드블럭들이 입력된다.
서로 다른 동작을 가지는 클래스들도 공통된 기능을 뽑아 상속 구조로 사용할 수 있다. 일반 클래스 간에 상속관계를 구축할 경우, 재정의를 해도 되고 안해도 되기 때문에 다형성을 보장할 수 없다. 추상 클래스로 상속관계를 구축한 경우에는 재정의가 필수기 때문에 다형성은 보장되지만, 구현메서드를 가질 수 있기 때문에 하위클래스가 오작동할 수 있다(=구현메서드가 작동하면 안되는 클래스에서 작동할 수 있다).
// 상위클래스
public abstract class RemoteControl {
// 추상메서드
//chUp, chDown, volUp, volDown()
public abstract void chUp();
public abstract void chDown();
public abstract void volUp();
public abstract void volDown();
// 구현메서드
public void internet(){
System.out.println("Internet is working");
}
}
// 하위클래스
public class Radio extends RemoteControl{
@Override
public void chUp() {
System.out.println("Radio channel goes up");
}
@Override
public void chDown() {
System.out.println("Radio channel goes down");
}
@Override
public void volUp() {
System.out.println("Radio volume goes up");
}
@Override
public void volDown() {
System.out.println("Radio volume goes down");
}
}
// Radio 클래스에는 필요없는 internet() 메서드가 포함되어 오작동한다.
// (오류는 나지 않으나, 설계상 작동해서는 안됨.)
public class InterfaceTest {
public static void main(String[] args) {
RemoteControl rc1 = new Radio();
rc1.chUp();
rc1.internet(); // 실행되면 안되는데 실행됨
}
}
따라서 구현메서드를 가지지 않는 추상클래스를 사용하게 되는데, 이를 인터페이스라고 한다.
서로 다른 동작을 가지는 클래스를 상속관계로 묶을 때는 인터페이스를 사용하면 된다.
인터페이스는 추상메서드만 포함하고, 구현메서드는 올 수 없는 추상클래스를 말한다.
인터페이스를 선언할 때는 접근제어자 뒤에 interface라고 명시해주면 된다. (abstract는 생략해도 된다.)
또, 인터페이스를 사용하여 클래스를 만들 때는 상속에서의 extends처럼 implements라는 키워드를 써주어야 한다. 이때 implement는 구현했다 라는 뜻으로 이해하면 된다.
인터페이스도 추상클래스와 마찬가지로 단독으로 객체생성이 불가능하다.
상속을 화살표로 표시했다면, 인터페이스는 <<인터페이스>> 처럼 부등호(?) 2개로 감싸고 흘림체로 인터페이스 이름을 작성하여 표시한다.
// 인터페이스
// 구현메서드는 삭제한다.
public interface RemoteControl {
public void chUp();
public void chDown();
public void volUp();
public void volDown();
public void internet();
// TV 클래스
public class TV implements RemoteControl{
public void chUp() {
System.out.println("TV channel goes up");
}
public void chDown() {
System.out.println("TV channel goes down");
}
public void volUp() {
System.out.println("TV volume goes up");
}
public void volDown() {
System.out.println("TV volume goes down");
}
public void internet() {
System.out.println("TV is connected to internet");
}
}
인터페이스에는 추상메서드와 함께 final static이라는 상수를 사용할 수 있다(순서를 바꿔서 static final이라고 써도 된다). static은 인터페이스가 단독으로 객체를 생성할 수 없기 때문에 붙었다.
일반적으로 상수를 할당할 때 이름은 전부 대문자로 작성한다. 상수를 설정하여, 예를 들어 chUp()이라는 메서드를 실행시킨다고 할 때 최대로 올라거나 내려갈 수 있는 채널 수에 제한을 두는 식으로 사용할 수 있다.
그냥 숫자를 넣기보다 상수에 이름을 붙여 반복문에 사용하는 것이 가독성 측면에서 훨씬 바람직하기도 하다.
일반적으로 자바는 단일상속을 지원하지만, 인터페이스는 다중상속을 지원한다. extends는 1개 밖에 받지 못하지만, implement는 여러개 받을 수 있어 다중상속이 가능해진다.
// 예시
public class Dog extends Animal implements Pet, Robots{
...
}
인터페이스는 인터페이스를 상속할 수 있다. 예를 들어 인터페이스 A와 B가 있고, C라는 클래스가 있다고 할 때,
public interface B extends A{}
가 가능하고,
public class C implements B{}
가 가능한 것이다.
B로든 A로든 둘 다 C를 업캐스팅할 수 있다.
- 다형성을 보장하기 위해 등장
- 추상 메서드를 가질 수 있음
- 단독 객체 생성 불가
- 부모 역할로 사용(Upcasting)
추상클래스는 서로 비슷한 클래스의 공통부분을 묶을 때 사용하며, 구현메서드도 받을 수 있다.
단일상속만 가능하다.
인터페이스는 서로 다른 클래스의 공통부분을 묶을 때 사용하며, 추상메서드와 final static 상수만을 받을 수 있다.
1개 이상의 인터페이스를 구현할 수 있다.