티스토리 뷰

* 본 게시물은 HeadFirst Java 책을 공부목적으로 정리한 것 입니다.

8. 인터페이스와 추상 클래스

인터페이스

상속은 시작에 불과, 다형성을 제대로 사용하려면 인터페이스가 필요.

인터페이스란?  100% 추상 클래스. 추상 클래스(abstract class)란 바로 인스턴스를 만들 수 없는 클래스.

Wolf aWolf = new Wolf(); ---> 가능

Animal aHippo = new Hippo(); ---> 가능

* aHippo는 Hippo 객체에 대한 Animal 레퍼런스

Animal anim = new Animal(); ---> 가능 할까?

> Animal 유형의 객체를 만들려고 하는 것은 스타 트렉의 전송 과정에서 사고가 일어난 것과 비슷. 어딘가로 전송하는 과정에서 버퍼에 뭔가 안 좋은 일이 생기는 것과 같은 결과가 나올 수 있음. 그렇다면 어떻게 해결?


상속과 다형성을 위해 Animal 클래스가 분명 필요하지만 Animal 클래스 자체가 아닌 덜 추상적인 Animal 클래스의 하위 클래스의 인스턴스만 만들 수 있게 하면 좋음.

우리가 원하는 것은 Tiger객체나 Lion객체지 Animal 객체가 아니니까.

어떤 클래스의 인스턴스를 만들 수 없게 하려면 즉, 특정 유형에 대해 “new” 키워드를 쓸 수 없게 하려면 클래스를 abstract로 지정하면 컴파일러에서 그 유형의 인스턴스를 만드는 코드를 허용하지 않음.


하지만, 그 추상 유형(abstract type)을 레퍼런스로 사용 할 수 있음.

사실 그렇게 레퍼런스로 사용하는 것이 바로 이런 추상 클래스를 만드는 핵심 이유중 하나.

(다형적인 인자나 리턴 유형을 쓰기 위해 또는 다형적인 배열 만들기 위해 씀)

추상클래스

클래스의 상속 구조를 설계할 때는 클래스를 추상 클래스로 만들지 아니면 구상 클래스(concrete class)로 만들지를 결정해야 함.

구상 클래스 = 인스턴스를 만들어도 될 만큼 구체적인 클래스

즉 구상 클래스에 대해서는 그 유형의 객체 만들어도 됨.


abstract class Canine extends Animal {
   public void roam() { }
}


컴파일러에서는 추상 클래스의 인스턴스를 만드는 것을 허용하지 않음

왜냐면 Wolf객체,Dog객체를 만드는 것은 이해가 되지만 Animal객체는 정확하게 어떤 것 일까? 어떻게 생겼을까? Animal 유형의 객체를 만들려고 하는 것은 스타 트렉의 전송 과정에서 사고가 일어나는 것과 비슷하다고 볼 수 있다. 어딘가로 전송하는 과정에서 버퍼에 뭔가 안 좋은 일이 생기는 것과 같은 결과가 나올 수 있음.


추상 클래스란, 아무도 그 클래스의 새로운 인스턴스를 만들 수 없는 클래스를 의미.

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


추상 클래스확장하지 않으면 거의 쓸모도 없고, 가치도 없고, 삶의 목적도 없음.

추상 클래스를 만들었을 때 실제 실행 중에 일을 처리하는 것은 그 추상 클래스의 하위 클래스 인스턴스.

추상 메소드

클래스뿐만 아니라 메소드도 abstract로 지정할 수 있다. 추상 클래스는 반드시 확장해야 하는 클래스 의미. 추상 메소드는 반드시 오버라이드해야하는 메소드를 의미.

추상 메소드에는 몸통이 없음. 적당한 코드를 생각할 수 없는 메소드를 추상 메소드로 만들기 때문. 그냥 세미콜론을 써서 선언을 끝내면 됨.

public abstract void eat();


추상메소드를 만들 때는 클래스도 반드시 추상 클래스로 만들어야 함.

하지만 추상 클래스 안에 추상 메소드와 추상 메소드가 아닌 메소드를 모두 집어 넣을 수 있음


추상메소드를 만드는 이유는 실제 메소드 코드를 전혀 집어넣기 않았더라도 일련의 하위 클래스를 위한 규약(protocol)의 일부를 정의하기 위한 것.

다형성을 활용하기 위해 “이 유형에 속하는 모든 하위클래스 유형에는 이 메소드가 있어야 한다”는 것을 지정하기 위해 필요한 것.


추상메소드는 모두 구현해야 함.

여기서 구현 = 메소드를 오버라이드 하는 것

추상메소드는 몸통이 없음. 다형성을 위해 존재할 뿐.

자바에서 신경쓰는 부분은 우리가 만든 구상 하위클래스에 메소드가 들어있어야 한다는 것.


모든 종류의 Animal 하위클래스를 받아들일 수 있는 AnimalList 클래스 생성

* Animal[] animals = new Animal[5];

이 부분은 새로운 Animal 객체를 만드는 것이 아닌 Animal 유형의 배열 객체를 새로 만드는 것

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;
           nextIndex++;
       }
   }
}


public class AnimalTestDrive{
   public static void main (String[] args){
   MyAnimalList list = new MyAnimalList();
   Dog a = new Dog();
   Cat c = new Cat();
   list.add(a);
   list.add(c);
   }
}   

Object

어떤 것이든 받아들일 수 있는 더 포괄적인 클래스

자바에서 모든 클래스는 Object라는 클래스를 확장한 것. 즉, 모든 것의 상위클래스

어떤 클래스를 만들더라도 그 클래스는 반드시 Object 클래스를 확장한 클래스로 만들어짐.

Animal은 Object를 직접 확장하는 클래스, 다른건 간접 확장.

ArrayList 메소드 중에는 궁극의 다형적인 유형인 Object를 사용하는 것이 많음.

자바의 모든 클래스는 Object의 하위클래스이므로 그러한 ArrayList 메소드 에서는 어떤 것이든 받아들일 수 있음.

boolean remove(Object elem)


Object 클래스에는 무엇이 들어있을까?


* 메소드 일부

booleanequals()   ||   Class getClass()   ||   int hashCode()   ||  String toString()


새로 만드는 클래스는 무조건 Object 클래스의 모든 메소드를 상속받게 됨. 우리가 전에 만들었던 메소드에도 알고 보면 이런 메소드가 상속되어 있음.


1. equals(Object o) : 두 객체를 ‘같은’것으로 볼 수 있을지 판단하는 메소드

Dog a = new Dog();
Cat c = new Cat();
if (a.equals(c)){
   System.out.println("true");
} else {
   System.out.println("false");


>> false


2. getClass() : 어떤 클래스의 인스턴스인지 알 수 있도록 그 객체의 클래스를 리턴

Cat c = new Cat();
System.out.println(c.getClass());

>> class Cat


3. hashCode() : 그 객체에 해당하는 해시코드(일단 고유ID라고 생각)를 출력

Cat c = new Cat();
System.out.println(c.hashCode());

>> 8202111


4. toString() : 클래스명과 몇 가지 별로 잘 쓰이지 않는 숫자가 포함된 String 메시지 출력

Cat c = new Cat();
System.out.println(c.toString());

>> Cat@7d277f


Q) Object클래스는 추상 클래스 인가?

>  No. 모든 클래스에서 무조건 오버라이드 할 필요 없이 그대로 사용할 수 있는 메소드를 구현해놓은 코드가 들어있어서.


Q) Object 객체를 만드는 것의 의미

>  Object 객체가 필요한 이유는 그냥 포괄적인 개념의 ‘객체’가 필요한 경우가 종종 있어서 스레드 동기화를 할 때 많이 사용. Object객체를 만들 수 있지만 별로 없다는 정도만 알아두기.


Q) Object 유형은 주로 다형적인 인자나 리턴 유형으로 쓰인다고 할 수 있는 가?

> Object클래스는 주로 두 가지 용도로 쓰임.

  1. 임의 클래스에 대해 어떤 작업을 하는 메소드를 만들 때 다형적 유형으로 사용.

> equals(Object o)

  1. 자바에 있는 모든 객체에서 실행 중에 필요한 진짜 메소드 코드를 제공하기 위해서.

> String toString()


Q) 왜 모든 메소드의 인자와 리턴 유형은 Object로 하지 않는가?

> ‘유형 안정성’이 완전히 무의미 해짐. 어떤 객체를 Object 레퍼런스 유형을 써서 참조하면 자바에서는 항상 그 레퍼런스가 Object 유형의 인스턴스를 참조하고 있다고 생각해서 Object클래스에서 선언한 메소드만 호출 할 수 있음.

Object o = new Ferrari();
o.goFast(); // 안됨

자바는 유형을 철저하게 따지는 언어기 때문에 컴파일러에서 어떤 객체에 대한 메소드를 호출할 때 그 객체가 주어진 메소드에 대해 응답을 할 수있는가를 따져봄. 즉, 그 레퍼런스 유형의 클래스에 해당 메소드가 있는 경우에만 객체 레퍼런스에 대해 메소드를 호출할 수 있음.


Object 유형의 다형적 레퍼런스를 쓸 때 치뤄야 할 대가

어떤 다른 유형의 인스턴스를 만들지만 레퍼런스만 Object 유형을 사용하는 경우.

ArrayList<Object> myDogArrayList = new ArrayList<Object>();
Dog a Dog = new Dog();
myDogArrayList.add(aDog);
Dog d= myDogArrayList.get(0); //get메소드의 return유형이 Object이므로 컴파일 에러!

ArrayList<Object>에서 나오는 객체는 레퍼런스 유형과 상관 없이 무조건 Object 유형의 레퍼런스로 나옴.

객체가 들어갈 때는 각각 SoccerBall, Fish,Guitar,Car유형으로 들어가지만, 나올때는 모두 Object유형으로 나옴. Object클래스의 인스턴스 인 것 처럼 행동함. 컴파일러에서는 그 객체가 Object가 아닌 다른 클래스의 인스턴스라고 가정할 수가 없음.

Object o = a1.get(index);
int i = o.hashCode(); //이건 가능. Object 클래스에 hashCode()메소드가 있음.
o.bark(); //컴파일 에러. Object에는 bark()메소드가 없음. 그래서 모름.


*컴파일러에서 어떤 메소드를 호출할 수 있는지 결정할 때는 실제 객체 유형이 아닌 레퍼런스 유형을 기준으로 따짐. 나는 그 객체에 어떤 기능이 있는지 알아도 컴파일러는 무조건 Object객체로 생각.


객체 레퍼런스를 실제 유형으로 캐스트 하는 방법

Object o = a1.get(index);
Dog d = (Dog) o; //그 객체를 원래대로 Dog으로 캐스트
d.roam();


*Dog인지 잘 모르겠으면 instanceof 연산자를 써서 확인 가능.

if(o instanceof Dog){
Dog d = (Dog) o;
}

인터페이스

자바에서는 다중 상속을 쓸 수 없음. ‘죽음의 다이아몬드’라고 알려져 있는 문제가 있기 때문.

> 최상위 클래스의 burn()메소드를 상위 클래스 2개가 상속 받음. 각자 오버라이드 함.  하위 클래스에서는 burn()메소드를 실행하면 어떤 메소드가 실행될까? 이런 골치 아픈 문제 때문에 자바에서는 쓸 수 없게 함.

다중 상속이 필요한데 쓸 수 없게 해서 자바에서는 다른 해결책을 제공.

그것이 ‘인터페이스’ 모든 메소드를 추상 메소드로 만드는 것.

자바 인터페이스는 100% 순수한 추상 클래스와 비슷.

//인터페이스 정의
public interface Pet {...}
> "class" 대신 "interface" 키워드를 사용.
//인터페이스 구현
public class Dog extends Canine implements Pet{...}
> "implements" 뒤에 인터페이스명을 지정.


Pet 인터페이스 제작과 구현

//<제작>
public interface Pet{
   void beFriendly();
   void play();
}
//<구현>
public class Dog extends Canine implements Pet{


//반드시 구현해야함.    
   public void beFriendly(){...}
   public void play(){...}
   
//일반적으로 오버라이드 하는 메소드
   public void roam(){...}
   public void eat(){...}
}


인터페이스를 다형적인 유형으로 사용하면 어떤 상속 트리에 있는 객체도 집어넣을 수 있음.

서로 다른 상속 트리에 들어있는 클래스에서 공통적인 인터페이스를 구현할 수 있게 하는 것은 자바API에서 매우 중요하게 작용함. 그리고 더 좋은 점은 한 클래스에서 인터페이스 여러 개를 구현할 수도 있음.


어떤 클래스를 하위클래스로 만들지, 추상 클래스로 만들지, 아니면 인터페이스로 만들지 어떻게 결정 할 수 있을까?

> 클래스를 새로 만들 때 그 클래스가 다른 어떤 유형에 대해서도 ‘A는 B다’ 테스트를 통과할 수 없다면 그냥 클래스로 만듬.

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

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

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


상위클래스에 있는 버전의 메소드를 호출하는 방법

> super.runReport();


*핵심 정리

> 클래스를 만들 때 인스턴스를 만들 수 없게 하고 싶다면?

- abstract 키워드 사용.(추상클래스)

> 추상 클래스에는 추상 메소드와 그렇지 않은 메소드를 모두 집어넣을 수 있음.

- 추상 메소드는 본체가 없어서 반드시 오버라이드 해야하고, 일반 메소드는 오버라이드 해도 되고 안 해도 됨.

> 클래스에는 추상 메소드가 하나라도 있으면 그 클래스는 추상 클래스로 지정.

> 추상 메소드에는 본체가 없으며 선언 부분은 세미콜론으로 끝남.

> 상속 트리에서 처음으로 나오는 구상 클래스에서는 반드시 모든 추상 메소드를 구현해야함.

> 자바에 들어있는 모든 클래스는 직/간접적으로 Object의 하위 클래스.

> 메소드를 선언 할 때 인자,리턴 유형을 Object로 지정해도 됨.

> 어떤 객체에 대해 메소드를 호출하려면 그 객체를 참조하는 레퍼런스 변수 유형의 클래스에 그 메소드가 있어야만 함.

> Object 유형의 레퍼런스 변수는 캐스팅을 하지 않고는 다른 유형의 레퍼런스에 대입할 수 없음.

> ArrayList<Object>에 나오는 객체는 모두 Object 유형으로 나옴.

> “죽음의 다이아몬드” 문제때문에 자바에서는 다중 상속을 허용하지 않음. 클래스는 단 하나만 확장할 수 있음.

> 인터페이스는 100% 순수한 추상 클래스. 인터페이스에서는 추상 메소드만 정의.

> 인터페이스를 만들 때 class 대신 interface라는 키워드를 사용함.

> 인터페이스를 구현 시 implements라는 키워드를 사용.

> 클래스를 만들 때 인터페이스를 여러 개 구현할 수 있음.

> 인터페이스를 구현하는 클래스에서는 인터페이스에 들어있는 모든 메소드를 구현해야 함.

> 하위 클래스에서 어떤 메소드를 오버라이드 했는데, 상위 클래스 버전을 호출하고 싶다면 super라는 키워드를 사용.



댓글