8장. 심각한 다형성 “추상”과 “구상”의 차이점에 대해 알아봅니다. Object 클래스에 대해 알아봅니다. 다형성에 대해 알아봅니다. 캐스팅에 대해 알아봅니다. 인터페이스에 대해 알아봅니다.
인터페이스와 추상 클래스
Animal picture food hunger boundaries location makeNoise() eat() sleep() roam Feline roam() Canine roam() Hippo Lion Cat Dog Wolf Tiger
Wolf aWolf = new Wolf(); Wolf 객체에 대한 Wolf 레퍼런스 Wolf aWolf = new Wolf(); Wolf 객체 aWolf Wolf
Hippo 객체에 대한 Animal 레퍼런스 Animal aHippo = new Hippo(); Hippo 객체 aHippo Animal
Animal 객체에 대한 Animal 레퍼런스 ? Animal anim = new Animal(); ? Animal 객체 anim Animal
객체는 어떻게 생겼을까? Animal 객체는 어떻게 생겼을까요?
추상 클래스 추상 클래스(abstract class)란? 인스턴스를 만들 수 없는 클래스 반드시 확장해야 함 추상 유형을 레퍼런스로 사용할 수는 있음 다형적인 인자, 리턴 유형, 배열 등에 활용 abstract class Canine extends Animal { public void roam() { } }
추상 클래스 abstract public class Canine extends Animal { public void roam() { } } public class MakeCanine { public void go() { Canine c; c = new Dog(); c = new Canine(); c.roam(); % javac MakeCanine.java MakeCanine.java:5: Canine is abstract; Cannot be instantiated c = new Canine(); ^ 1 error
추상 vs. 구상 추상 추상 추상 구상 구상 구상 구상 구상 구상 Component Animal JButton JList JComboBox Canine Feline JLabel 구상 구상 JScrollPane Lion Hippo 구상 Cat 구상 구상 Tiger 구상 Dog Wolf
추상 메소드 추상 메소드 (abstract method) 몸통이 없는 메소드 반드시 오버라이드해야 합니다. public abstract void eat(); 추상 메소드를 만들 때는 클래스도 반드시 추상 클래스로 만들어야 합니다. 추상 클래스가 아닌 클래스에는 추상 메소드를 집어넣을 수 없습니다.
바보 같은 질문은 없습니다. 추상 클래스는 하위클래스에서 상속해서 쓸 공통적인 코드를 집어넣기 위해 있는 거라고 볼 수 있지만 추상 메소드는 왜 있는 거죠? 추상 클래스에서 하위 클래스에서 활용할 수 있는 일반적인 메소드 코드를 만들 수 없는 경우에 추상 메소드를 만듭니다. 하위클래스에 반드시 있어야 하는 일련의 메소드를 정의하기 위해 추상 메소드가 필요합니다.
바보 같은 질문은 없습니다. 그러면 어떤 장점이 있나요? 다형성이 가장 중요한 장점입니다. 추상 메소드는 다형성을 활용하기 위해 “이 유형에 속하는 모든 하위클래스 유형에는 이 메소드가 있어야 한다”는 것을 지정하기 위해서 반드시 필요합니다.
추상 메소드는 모두 구현해야 합니다. 추상 메소드 구현 == 메소드 오버라이드 상속 트리에서 처음으로 등장하는 구상 클래스에서 모든 추상 메소드를 구현해야만 합니다. 추상 메소드에는 추상 메소드와 구상 메소드가 모두 들어갈 수 있지만 구상 메소드에는 추상 메소드가 들어갈 수 없습니다. 메소드 서명과 리턴형이 똑같은 추상 메소드가 아닌 메소드를 만들기만 하면 됩니다. (자바에서는 메소드의 코드가 어떻게 되는지는 신경 쓰지 않습니다.)
다형성 활용 ArrayList 비슷한 클래스를 직접 만듭시다. 배열의 길이는 5로 제한 (Dog 배열로 시작) add() 메소드를 호출하면 새로 받아온 Dog 객체를 추가하고 인덱스(nextIndex)를 증가시킴 public class MyDogList { private Dog[] dogs = new Dog[5]; private int nextIndex = 0; public void add(Dog d) { if (nextIndex < dogs.length) { dogs[nextIndex] = d; System.out.println(“Dog added at “ + nextIndex); nextIndex++; }
Cat 객체도 집어넣으려면? Cat 객체를 저장하기 위해 MyCatList라는 클래스를 따로 만듭니다. 서로 다른 두 배열과 서로 다른 두 메소드(addCat(Cat c), addDog(Dog d))가 들어있는 DogAndCatList라는 클래스를 만듭니다. 모든 Animal 하위클래스를 받아들일 수 있는 AnimalList 클래스를 만듭니다.
MyAnimalList public class MyAnimalList { private Animal[] animals = new Animal[5]; private int nextIndex = 0; public void add(Animal a) { if (nextIndex < animals.length) { animals[nextIndex] = a; System.out.println(“Animal added at “ + nextIndex); nextIndex++; }
AnimalTestDrive 클래스 public class AnimalTestDrive { public static void main(String[] args) { AnimalList list = new AnimalList(); Dog a = new Dog(); Cat c = new Cat(); list.add(a); list.add(c); } % java AnimalTestDrive Animal added at 0 Animal added at 1
Animal 말고 다른 객체는요? 가장 포괄적인 클래스는? Object 클래스!!!
Object 클래스에는 무엇이 있나요? equals(Object o) Dog a = new Dog(); Cat c = new Cat(); if (a.equals(c)) { System.out.println(“true”); } else { System.out.println(“false”); } Object boolean equals() Class getClass() int hashCode() String toString() YourClassHere % java TestObject false
Object 클래스의 메소드 getClass() Cat c = new Cat(); System.out.println(c.getClass()); % java TestObject class Cat
Object 클래스의 메소드 hashCode() Cat c = new Cat(); System.out.println(c.hashCode()); % java TestObject 8202111
Object 클래스의 메소드 toString() Cat c = new Cat(); System.out.println(c.toString()); % java TestObject Cat@7d277f
바보 같은 질문은 없습니다. Object 클래스는 추상 클래스인가요? 아닙니다. 모든 클래스에서 무조건 오버라이드할 필요 없이 그대로 사용할 수 있는 메소드를 구현해놓은 코드가 들어있기 때문에 추상 클래스가 아닙니다.
바보 같은 질문은 없습니다. 그러면 Object에 들어있는 메소드를 오버라이드할 수는 있나요? 할 수 있는 것도 있지만 final로 지정되어있어서 오버라이드할 수 없는 것도 있습니다. 될 수 있으면 hashCode(), equals(), toString() 메소드는 오버라이드하는 것이 좋습니다. getClass 같은 메소드는 반드시 특정한 방식으로 작동을 해야 하기 때문에 final로 지정되어있습니다. 그리고 고유 코드(native code)로 만들어진 메소드도 있습니다.
바보 같은 질문은 없습니다. ArrayList는 범용으로 쓸 수 있다고 했는데 왜 ArrayList<DotCom> 같은 식으로 써서 제한을 가하나요? 예전에는 무조건 ArrayList<Object> 같은 식이었습니다. 하지만 자바 5.0에서 <유형> 같은 매개변수화된 유형 기능이 추가되어 이제 특정 유형의 객체만 들어갈 수 있도록 제한할 수 있습니다. 이렇게 되면 나중에 ArrayList에서 객체를 꺼낼 때 훨씬 편하게 쓸 수 있습니다.
바보 같은 질문은 없습니다. Object 클래스도 Animal 클래스처럼 “객체”를 만들기가 이상하지 않나요?
바보 같은 질문은 없습니다. Object 유형은 주로 다형적인 인자/리턴형으로 쓰인다고 할 수 있는 건가요? ArrayList에서처럼 말이죠? 임의 클래스에 대해 어떤 작업을 하는 메소드를 만들 때 다형적 유형으로 쓰이는 경우 실행 중 자바에 들어있는 모든 객체에서 필요한 진짜 메소드를 제공하는 경우 스레드와 관련된 메소드도 매우 중요하게 쓰입니다.
바보 같은 질문은 없습니다. 왜 모든 메소드의 인자와 리턴 유형을 Object로 하지 않나요? 유형 안전성(type-safety) 문제 레퍼런스 유형에서 정의되어있는 메소드만 호출할 수 있습니다. Object o = new Ferrari(); o.goFast(); Object 유형의 레퍼런스로 참조한 객체에 대해서는 Object 클래스에 정의되어있는 것만 주문할 수 있습니다.
ArrayList<Object> 다형적 레퍼런스 어떤 객체를 ArrayList<Dog>에 집어넣으면 그 객체는 Dog로 저장되고, 꺼낼 때도 무조건 Dog 객체가 됩니다. ArrayList<Object>에 저장하면 그 객체는 원래 자기가 무슨 객체인지 기억하질 못합니다. ArrayList<Object> Object Object Object Object
기억상실증에 걸린 듯한 객체 public void go() { Dog aDog = new Dog(); Dog sameDog = getObject(aDog); } public Object getObject(Object o) { return o; Object o = al.get(index); DogPolyTest.java:10: incompatible types found : java.lang.Object required : Dog Dog sameDog = getObject(aDog); ^ 1 error
객체의 유형과 레퍼런스의 유형 Object o = al.get(index); int i = o.hashCode(); o.bark(); Dog 객체 o 메소드를 호출할 수 있는지 결정할 때는 객체가 아닌 레퍼런스의 유형을 기준으로 합니다. Object Object equals() getClass() hashCode() toString()
객체 안에 들어있는 알맹이 Snowboard 객체 Object Snowboard Object Snowboard equals() getClass() hashCode() toString() Snowboard Object Snowboard equals() getClass() hashCode() toString() turn() shred() getAir() loseControl() Snowboard 객체
다형성 여러 형태 s Snowboard 객체 o Snowboard s = new Snowboard(); Object o = s; Snowboard Object s Snowboard 객체 o
레퍼런스 캐스팅 Object o = al.get(index); Dog d = (Dog) o; d.roam(); Dog 객체 o Object d 컴파일러에서는 레퍼런스가 참조하는 실제 객체의 클래스가 아닌 레퍼런스 변수를 선언할 때 지정한 유형의 클래스를 확인합니다. Dog if (o instanceof Dog) { Dog d = (Dog) o; …
계약서 어떤 객체에 있는 메소드를 호출하려면 그 메소드가 레퍼런스 변수의 클래스에 들어있어야만 합니다. 클래스에 있는 public 메소드는 계약서, 즉 외부와의 약속이라고 생각하면 됩니다. 컴파일러에서는 레퍼런스가 참조하는 실제 객체의 클래스가 아닌 레퍼런스 변수를 선언할 때 지정한 유형의 클래스를 확인합니다.
계약서를 고쳐야 한다면? Dog 클래스는 동물 시뮬레이션용으로는 적합합니다. 애완동물(Pet 객체)의 행동인 beFriendly(), play() 같은 메소드가 있어야 합니다. 그냥 Dog 클래스에 이런 메소드를 바로 추가하면 될까요?
Dog를 Pet으로… 첫 번째 방법 애완동물의 성질을 나타내기 위한 메소드를 Animal 클래스에 집어넣습니다. 장점 단점 기존의 하위클래스를 전혀 건드리지 않아도 되고 새로 만드는 하위클래스에서도 그런 메소드를 사용할 수 있습니다. 단점 하마, 사자, 늑대 같은 것은 애완동물로 잘 키우지 않죠? 애완동물이 아닌 동물에게 애완동물의 행동을 부여하는 것이 적절치 않습니다. 각 애완동물마다 행동이 많이 다르기 때문에 일일이 수정해야 합니다.
Dog를 Pet으로… 두 번째 방법 메소드를 추상 메소드로 만들어서 오버라이드해야만 쓸 수 있도록 만듭니다. 장점 단점 구상클래스에서는 무조건 코드를 만들어야 합니다. 일부 유형에만 적용할 것을 Animal 클래스에 집어넣는다는 자체가 잘못된 접근법이라고 할 수 있습니다.
Dog를 Pet으로… 세 번째 방법 애완동물용 메소드를 사용할 클래스에만 집어넣습니다. 장점 단점 애완동물이 아닌 동물에 애완동물용 메소드가 들어갈 걱정을 하지 않아도 됩니다. Dog나 Cat에서는 그런 메소드를 구현할 수 있지만 다른 클래스에서는 전혀 그런 메소드를 쓸 수가 없도록 할 수 있습니다. 단점 제대로 된 계약서를 갖춰야 합니다. 다형성을 적용할 수가 없습니다.
두 개의 상위클래스? 필요한 것 두 개의 상위클래스?? 애완동물의 행동을 Pet 클래스에만 집어넣는 방법 모든 애완동물 클래스에 똑같은 메소드가 정의되게 하는 방법 각 애완동물마다 다른 인자, 리턴 유형, 배열을 사용하지 않고도 다형성을 활용하여 모든 애완동물에 대해 애완동물용 메소드를 호출할 수 있도록 하는 방법 두 개의 상위클래스??
두 개의 상위클래스? Animal Pet Canine Feline Lion Hippo Cat Tiger Dog Wolf
다중 상속 다중 상속(multiple inheritance)의 문제점 죽음의 다이아몬드 DigitalRecorder int i burn() CDBurner burn() DVDBurner burn() 자바에서는 다중 상속을 허용하지 않습니다. ComboBurner
인터페이스 (interface) 인터페이스 모든 메소드가 추상 메소드입니다. 하위클래스에서 반드시 구현해야만 하므로 상속받은 것 중에 어떤 것을 호출해야 할지 결정할 수 없게 되는 문제가 생기지 않습니다. 정의 방법 public interface Pet {…} 구현 방법 public class Dog extends Canine implements Pet {…} Pet abstract void beFriendly(); abstract void play();
Pet 인터페이스 정의 및 구현 public interface Pet { public abstract void beFriendly(); public abstract void play(); } public class Dog extends Canine implements Pet { public void beFriendly() {…} public void play() {…} public void roam() {…} public void eat() {…}
바보 같은 질문은 없습니다. 인터페이스에서는 전혀 코드를 구현할 수 없으니까 진정한 의미에서 다중 상속 기능을 제공한다고 할 수 없지 않나요? 모든 메소드가 추상 메소드라면 인터페이스를 왜 사용해야 하나요? 다형성 때문입니다!!! 인자나 리턴 유형으로 구상 클래스 대신 인터페이스를 사용하면 어떤 객체든 그 자리에 들어갈 수 있습니다. 객체의 역할을 기준으로 해서 처리할 수 있습니다. 인터페이스의 특성상, 구상 메소드를 쓸 수 있더라도 대부분 오버라이드해서 써야 하는 메소드를 정의합니다.
인터페이스 Robot Animal Pet Agent RoboDog Canine Feline Lion Hippo Cat Tiger Dog Wolf
인터페이스 서로 다른 상속 트리에서 같은 인터페이스를 구현할 수 있습니다. 한 클래스에서 여러 개의 인터페이스를 구현할 수도 있습니다. public class Dog extends Animal implements Pet, Saveable, Paintable {…}
핵심 정리 클래스를 만들 때 인스턴스를 만들 수 없게 하고 싶다면 abstract 키워드를 사용하면 됩니다. 추상 클래스에는 추상 메소드와 추상 메소드가 아닌 메소드를 모두 집어넣을 수 있습니다. 클래스에 추상 메소드가 하나라도 있으면 그 클래스는 추상 클래스로 지정해야 합니다. 추상 메소드에는 본체가 없으며 선언 부분은 세미콜론으로 끝납니다. 상속 트리에서 처음으로 나오는 구상 클래스에서는 반드시 모든 추상 메소드를 구현해야 합니다. 자바에 들어있는 모든 클래스는 직접 또는 간접적으로 Object의 하위클래스입니다. ArrayList<Object>에서 꺼내는 객체는 모두 Object 유형입니다.
핵심 정리 레퍼런스 변수를 캐스트해서 객체의 원래 유형으로 돌려놓을 수 있습니다. 어떤 객체에 있는 메소드를 호출하려면 그 메소드가 레퍼런스 변수의 클래스에 들어있어야 합니다. 자바에서는 다중 상속을 허용하지 않습니다. 클래스는 단 하나밖에 확장할 수 없습니다. 인터페이스는 100% 순수한 추상 클래스입니다. 인터페이스를 구현할 때는 implements라는 키워드를 씁니다. 한 클래스에서 여러 개의 인터페이스를 구현할 수 있습니다. 인터페이스의 모든 메소드는 public, abstract 메소드이므로 인터페이스를 구현하는 클래스에서는 모든 메소드를 구현해야만 합니다.
하위클래스? 추상 클래스? 인터페이스? 어떤 클래스가 다른 어떤 클래스에 대해서도 ‘A는 B다’ 테스트를 통과할 수 없다면 그냥 클래스를 만듭니다. “더 구체적인 클래스”를 만들고 싶다면 하위클래스를 만듭니다. 하위클래스에서 사용할 틀(template)을 정의하고 싶다면, 그리고 구현 코드가 조금이라도 있으면 추상 클래스를 사용합니다. 상속 트리에서의 위치에 상관없이 어떤 클래스의 역할을 정의하고 싶다면 인터페이스를 사용하면 됩니다.
super 키워드 오버라이드할 때 상위클래스 버전의 메소드의 기능이 필요하다면 어떻게 해야 하나요? 추상 클래스에 구상 메소드에서 필요한 포괄적인 작업을 처리하기 위한 코드를 미리 만들어두고 나중에 구상 메소드에서 더 구체적인 부분만 처리하도록 할 수 있습니다.
super 키워드 abstract class Report { void runReport() { // 보고서 설정 } void printReport() { // 포괄적인 출력 작업 class BuzzWordReport extends Report { super.runReport(); buzzwordCompliance(); printReport(); void buzzwordCompliance() { … } BuzzwordReport Report
숙제 본문을 다시 한 번 꼼꼼히 읽어봅시다. 본문 중간에 있는 각종 연습문제와 8장 끝에 있는 연습문제, 퍼즐을 풀어봅시다.