HeadFirst java(8.인터페이스, 추상클래스)

chaean Lee·2021년 1월 17일
0
post-thumbnail

📌추상클래스란?

상속편에서 나왔던 Animal tree를 머리 속으로 떠올려 보자.

Dog, Cat, Hippo... 클래스를 객체로 만드는 것은 이해가 되지만,

Animal 객체, 뭔가 이상하다. 하나의 외계생명체를 만드는 것과 같은 생각이 든다.

상속과 다형성을 위해 Animal 클래스는 필요하다.

프로그래머는 Animal 클래스의 하위 클래스 객체만 만들 수 있으면 좋지 않을까?

실제로 우리가 만드는 객체는 하위클래스이기 때문이다.

Animal 클래스를 객체로 만들지 못하게 하고, 대신에 그 레퍼런스를 사용 할 수 있으면 좋지 않을까?

이 레퍼런스를 사용하는 것이 추상 클래스를 만드는 핵심적인 이유이다.(다형적인 인자나 리턴유형을 쓰기 위해, 또는 다형적인 배열을 만들기 위해)

추상클래스란 새로운 객체를 만들수 없는 클래스를 의미한다.

하지만 다형성을 활용하기 위해 레퍼런스 유형을 선언할 때 추상클래스를 쓸수 있다.

📌추상 메소드

추상클래스는 반드시 확장해야 하는 클래스이다.

추상 메소드는 반드시 오버라이드 해야 하는 메소드이다. 이유는?

Animal클래스에 Bark()메소드를 구체적으로 정의를 내릴수 있을까? 적당한 코드를 생각할 수 없는 메소드를 추상 메소드로 만든다.

각 하위클래스에서 알아서 맞게 정의 내려서 사용할 수 밖에 없다.

그래서 상위클래스에서 몸통 없는 메소드를 만들고 각 하위 클래스에서 만들게끔 강제시킨다.

이러면 어떤 점이 좋을까?

물론 다형성이다. 상위클래스 유형을 메소드의 인자나 리턴유형 또는 배열 유형으로 쓸수 있기 때문이다.
추상 메소드는 다형성을 활용하기 위해 "이 유형에 속하는 모든 하위클래스 유형에는 이 메소드가 있어햐 한다" 는 것을 지정하기 위해 필요한 것이다.

📌Animal이 아닌 객체는 어떻게 하죠? 어떤 것이든 받아들이는 더 포괄적인 클래스를 만드는 것은 어떨까요?

자바에서 모든 클래스는 object라는 클래스를 확장한 것이다.

object 유형의 다형적 레퍼런스를 쓸 떄 치뤄야 할 대가...

ArrayList<Object> myDogArrayList = new ArrayList<Object>();
Dog adog = new Dog();
myDogArrayList.add(adog);

Dog d = myDogArrayList.get(0); //X  

ArrayList< Object > 를 사용할 때는 get()메소드에서의 리턴유형도 Object가 됩니다. 컴파일러에서는 그 객체가 Object의 하위 클래스라는 것만 알 수 있을 뿐 Dog인지 전혀 알 수가 없습니다.

📌다형성은 여러 형태를 의미합니다.

Dog를 Dog 또는 Object로 간주할 수 있다.

중요한 점은 Dog유형의 객체인 경우에도 그 Dog객체에 대한 Object 레퍼런스에서는 Dog에만 있는 메소드를 사용할 수 가 없다는 점이다.

Dog adog = new Dog();
Object o = adog;
o.bark();//XX 컴파일이 안됨.Object객체 메소드만 사용할 수 있다.
(Dog)o.bark()//

📌동물 상속 트리에서 애완동물 트리를 만들어야 한다면....(Pet 메소드를 추가하는 방법.)

📌그러면 다음과 같은 것이 필요하다.

✨애완동물의 행동을 Pet클래스에만 집어넣는 방법

✨모든 애완동물 클래스에 똑같은 메소드가 정의되게 하는 방법. 이 때 모든 프로그래머가 그런 메소드를 제대로 사용할 수 있게 해야 한다.(똑같은 이름, 똑같은 리턴유형을 가져야 하며 모든 메소드가 빠짐없이 있어야 합니다.)

✨각 애완동물 클래스마다 다른 인자 리턴유형 배열을 사용하지 않고도 다형성을 활용하여 모든 애완동물에 대해 애완동물용 메소드를 호출할 수 있게 하는 방법.

📌그런데 상위클래스를 두개 사용하는 경우 문제점이 있다.

그런 것을 '다중 상속'라고 부르는데, 자바에서는 다중 상속을 사용할 수 없다. 죽음의 다이아몬드라고 알려져 있는 문제가 있기 때문이다.

위 그림 맨 아래 클래스인 ComblDrive 에서 burn()메소드를 호출하면 어떤 메소드가 실행이 될까?

📌우리에겐 인터페이스가 있다.

해결방법은 모든 메소드를 추상 메소드로 만드는 것이다. 그러면 하위 클래스에서 반드시 메소드를 구현해야하므로 실행 중에JVM에서 상속받은 두 가지 버전 중 어떤 것을 호출해야 할 지 결정하지 못하는 문제가 생길 리 없다.

인터페이스 제작과 구현



public interface Pet{

	public abstract void beFriendly();
     void paly();             // 인터페이스 메소드는 자동으로 Public abstract이 되기 때문에 
                                 안 써도 된다.
                                (사실 사용하는 것은 그리 좋은 코딩 스타일이라고 할 수 없다.)
}

public class Dot extends Canine implements Pet{
    public void beFriendly(){...}
    public void play(){....}
    
    public void roam(){...}
    public void eat(){...}
}

질문: 모든 메소드가 추상메소드라면 인터페이스를 왜 사용해야 하나요?
다형성 때문입니다.
인자나 리턴 유형으로 구상 클래스 대신 인터페이스를 사용하면 그 인터페이스를 구현하는 것은 무엇이든 사용할 수 있다.
그리고 인터페이스가 있으면 클래스가 반드시 상속트리 하나로부터 나오지 않아도 됩니다.
클래스 하나는 클래스 하나를 확장하고 인터페이스 하나를 구현할 수 있습니다.
하지만 완전히 다른 상속 트리에서 나온 또 다른 클래스에서 같은 인터페이스를 구현할 수 도 있습니다.
따라서 객체를 그 인스턴스의 클래스 유형이 아닌 그 역할을 기준으로 처리할 수 있습니다.

📌정리(암기필)

👍클래스를 새로 만드려고 할 때 그 클래스가 다른 어떤 유형에 대해서도 'A는 B 다' 테스트를 통과할 수 없다면 그냥 클래스를 만든다.

👍어떤 클래스의 더 구체적인 버전을 만들고 어떤 메소드를 오버라이드하거나 새로운 행동을 추가해야 한다면 하위클래스를 만듭니다.(즉 클래스를 확장합니다.)

👍일련의 하위 클래스에서 사용할 틀을 정의하고 싶다면, 그리고 모든 하위클래스에서 사용할 구현 코드가 조금이라도 있다면 추상클래스를 사용합니다. 그리고 그유형의 객체를 절대 만들 수 없게 하고 싶다면 그 클래스를 추상 클래스도 만듭니다.

👍상속트리에서의 위치에 상관없이 어떤 클래스의 역할을 정의하고 싶다면 인터페이스를 사용하면 됩니다.

출처:headfirst (저자:케이시 시에라,버트 베이츠)

profile
왜! 언제! 어떻게! 😁

0개의 댓글