다형성을 이용하면 유지보수에 용이하고 확장성이 좋다. 다형성을 만족하기 위한 전제는 상속관계, 재정의, Upcasting, 동적바인딩을 만족해야 한다.
부모 클래스와 자식 클래스의 상속성을 만족하기 위해선 반드시 자식클래스에서 재정의가 이루어져야 한다. 강제로 재정의를 시키기 위해 부모 클래스의 함수에서 기능부분을 없애고 abstract를 추가하여 자식클래스의 재정의를 강제한다.
//부모 클래스
public abstract class Animal {
public abstract void eat();
// 추상메서드(불완전한 메서드) : 메서드의 구현부가 없다
}
eat() 메서트의 기능 구현부를 없애고 메서드와 클래스 앞에 abstract를 붙였다. 구현부를 없애면 자식 클래스에서 재정의를 하지 않았을 때 오류가 난다. 또한 eat()메서드에 다른 값을 정의를 해도 자식 클래스에서 재정의 하면 어차피 사용되지 않으므로 생략을 해도 상관없다.


재정의를 하지 않았을 때 오류가 발생하므로 추상클래스는 자식 클래스의 재정의를 강제한다. 다음과 같이 재정의를 해주어야 한다.
public class Cat extends Animal { // Animal -> eat(){ ? }
//재정의를 하지 않음(X) ---> 오류입니다(재정의를 하세요)
@Override
public void eat() {
System.out.println("고양이처럼 먹다");
}
public void night(){
System.out.println("밤에 눈에서 빛이 난다");
}
}
public class Dog extends Animal { //Animal -> eat(){ ? }
// 재정의를 하지 않음(X) --->오류입니다.(재정의를 하세요)
@Override
public void eat() {
System.out.println("개처럼 먹다");
}
}
public abstract class Animal {
//추상 메서드
public abstract void eat(); // 추상메서드(불완전한 메서드) : 메서드의 구현부가 없다
//구현 메서드
public void move(){
System.out.println("무리를 지어서 이동한다."); //
}
}
구현된 메서드는 자식 클래스들이 불러와서 사용할 수 있다.
일반 클래스에서는 부모클래스와 자식클래스 사이의 상속관계일 때 자식클래스에서 재정의를 해도되고 안해도 되기 때문에 다형성을 만족하기 위해서 강제로 재정의를 시키기 위해 추상클래스를 사용하였다. 하지만 추상클레스에서도 문제가 발생한다. 자식클래스 사이에 서로 다른 기능을 갖고 있을 때 추상클래스인 부모클래스에서 추상메서드가 아닌 구현된 메서드를 자식클래스들이 가질 수 있다. 아래 코드를 보며 설명하겠다.
//리모콘 부모클래스
public abstract class RemoCon {
//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("인터넷이 구동된다.");//비슷한 클래스로 자식클래스를 묶어줘야 구현메서드가 아무곳에 들어가도 문제가 생기지 않을 수 있다.
}//비슷한 경우에만 추상클래스 사용이 괜찮다. 비슷하지 않은 경우 구현메서드를 없애고 인터페이스로 묶어야 한다
//라디오 자식클래스
public class Radio extends RemoCon{
//chUp(), chDown(), volUp(), volDown()
@Override
public void chUp() {
System.out.println("Radio의 체널이 올라간다.");
}
@Override
public void chDown() {
System.out.println("Radio의 체널이 내려간다.");
}
@Override
public void volUp() {
System.out.println("Radio의 소리가 올라간다.");
}
@Override
public void volDown() {
System.out.println("Radio의 소리가 내려간다.");
}
}
//TV 자식클래스
public class TV extends RemoCon{
//chUp(), chDown(), volUp(), volDown()
@Override
public void chUp() {
System.out.println("TV체널이 올라간다.");
}
@Override
public void chDown() {
System.out.println("TV체널이 내려간다.");
}
@Override
public void volUp() {
System.out.println("소리가 올라간다.");
}
@Override
public void volDown() {
System.out.println("소리가 내려간다.");
}
}
//메인 메서드
public class InterfaceTest {
public static void main(String[] args) {
//리모콘으로 Radio와 TV를 동작시켜보자.
RemoCon remo = new Radio();
remo.chUp();
remo.chDown();
remo.volUp();
remo.volDown();
remo.internet(); // 라디오에서 인터넷이 되는것이 바람직하지 않다. → 추상클래스로 구현했기 때문에
remo = new TV();
remo.chUp();
remo.chDown();
remo.volUp();
remo.volDown();
remo.internet(); // 오동작
}
}
출력결과는 다음과 같다.

추상클래스를 사용했을 때 Radio는 인터넷이 구동되면 안된다. 하지만 부모클래스의 구현메서드에 의해 동작하지 말아야 할 자식클래스에서 동작할 수 있다. 그러므로 인터페이스가 필요하다.
인터페이스에선 추상클래스 대신 interface를 사용하고 부모클래스에서 구현메서드를 사용할 수 없다. 추상메서드를 의미하는 abstract를 묵시적으로 의미를 갖고 있으므로 생략할 수 있다. 또한 자식클래스 상속을 표현하는 extends 대신 implements를 사용한다.
//RemoCon 부모클래스
public interface RemoCon {
//chUp(), chDown(), volUp(), volDown()
public void chUp();
public void chDown();
public void volUp();
public void volDown();
public void internet();
}
//Radio 자식클래스
public class Radio implements RemoCon{
//chUp(), chDown(), volUp(), volDown()
@Override
public void chUp() {
System.out.println("Radio의 체널이 올라간다.");
}
@Override
public void chDown() {
System.out.println("Radio의 체널이 내려간다.");
}
@Override
public void volUp() {
System.out.println("Radio의 소리가 올라간다.");
}
@Override
public void volDown() {
System.out.println("Radio의 소리가 내려간다.");
}
@Override
public void internet() {
System.out.println("Radio에서는 인터넷이 지원이 안됩니다.");
}
}
//TV 자식클래스
public class TV implements RemoCon{
//chUp(), chDown(), volUp(), volDown()
@Override
public void chUp() {
System.out.println("TV체널이 올라간다.");
}
@Override
public void chDown() {
System.out.println("TV체널이 내려간다.");
}
@Override
public void volUp() {
System.out.println("소리가 올라간다.");
}
@Override
public void volDown() {
System.out.println("소리가 내려간다.");
}
@Override
public void internet() {
System.out.println("TV에서 인터넷이 지원됩니다.");
}
}
//메인클래스 테스트코드
public class InterfaceTest {
public static void main(String[] args) {
//리모콘으로 Radio와 TV를 동작시켜보자.
//다형성이 100%보장된다.
//부모가 인터페이스면 자식의 내부 동작방식을 전혀 몰라도 동작을 시킬 수 있다.
//RemoCon r = new Remocon(); 인터페이스는 자기자신 객체생성 불가
//부모의 역할을 할 수 있다.(명령은 보낼 수 있다.)
RemoCon remo = new Radio();
remo.chUp();
remo.chDown();
remo.volUp();
remo.volDown();
remo.internet();
remo = new TV();
remo.chUp();
remo.chDown();
remo.volUp();
remo.volDown();
remo.internet(); // 오동작
}
}
메인 메서드의 결과는 다음과 같다.
자바는 다중상속을 지원하지 않는다. 자바는 단일상속만 가능하지만 인터페이스를 이용해 필요한 자식에게 선택적으로 상속할 수 있다.

추상클래스에서 공통적인 특징을 상속하고 필요한 것은 인터페이스로 더 상속받을 수 있다. Dog클래스는 다음과 같이 표현할 수 있다.
public class Dog extends Animal implements Pet, Robots{
}
업캐스팅으로 통해 객체를 생성할 땐 다음과 같이 표현한다.
Animal r=new Dog();
Pet r=new Dog();
Robots r=new Dog();
