2008. 7. 27. 23:42

이클립스 유로파 버전 스크린캐스트

2008. 7. 24. 00:13

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

다형성을 제대로 사용하려면 100% 추상클래스인 인터페이스가 필요하다.
추상 클래스(abstract class)란 인스턴스를 만들 수 없는 클래스이다.

클래스를 추상 클래스로 만드는 방법은 클래스를 선언할 때 앞에 abstract 만 붙여주면 된다.
abstract class Canine extends Animal {
    public void roam() {
    }
}
추상 클래스란, 아무도 그 클래스의 새로운 인스턴스를 만들 수 없는 클래스를 의미한다.

abstract class Canine extends Animal {
    public void roam() {
    }
}
==============================================================
public class MakeCanine {
    public void go() {
        Canine c;           <- 상위클래스가 추상클래스인 경우에도 하위클래스 객체를 상위클래스 레퍼런스에
        c = new Dog();        대입하는 것은 가능하기 때문에 이 부분은 문제가 없다
        c = new Canine(); <- Canine 클래스는 추상클래스기 때문에 컴파일시 에러가 난다.
        c.roam();
    }
}

추상 클래스는 확장하지 않으면 쓸모가 없다.
추상 클래스를 만들었을 때 실제 실행 중에 일을 처리하는 것은 그 추상 클래스의 하위 클래스 인스턴스이다.
추상 클래스가 아닌 것을 구상클래스라고 한다. 상속트리의 맨 마지막 클래스들

추상 메소드
추상 클래스가 반드시 확장해야 하는 클래스라면 추상 메소드는 반드시 오버라이드 해야하는 메소드이다.
추상 메소드에는 몸통이 없다.
public abstract void eat();  <- 메소드 몸통이 없으므로 세미콜론으로 끝내면 된다.
추상 메소드를 만들 때는 클래스도 반드시 추상 클래스로 만들어야 한다.
추상 메소드를 만드는 이유는 실제 메소드 코드를 전혀 집어넣지는 않았더라도 일련의 하위클래스를 위한
규약(protocol)의 일부를 정의하기 위해서이다.
추상 메소드는 다형성을 활용하기 위해 "이 유형에 속하는 모든 하위클래스 유형에는 이 메소드가 있어야 한다"는
것을 지정하기 위해 필요하다.

"추상 메소드를 반드시 구현해야 한다"는 의미는 추상 메소드에서 선언한 리턴 유형과 호환가능한 리턴 유형을 가진 추상 메소드가 아닌 메소드를 만들어야 한다는 의미이다.

자바에서 모든 클래스는 Object라는 클래스를 확장한 것이다.
명시적으로 다른 클래스를 확장하지 않은 클래스는 자동으로 Object를 확장한 클래스로 정의됩니다.

Object 클래스를 대략 살펴보자.
equals(Object o) <- 두 객체를 '같은'것으로 볼 수 있을지 판단하는 메소드
getClass() <- 어떤 클래스의 인스턴스인지 알 수 있도록 그 객체의 클래스를 리턴한다.
hashCode() <- 그 객체에 해당하는 해시코드(고유 ID)를 출력한다.
toString() <- 클래스명과 몇가지 별로 잘 쓰이지 않는 숫자가 포함된 String 메시지 출력

Object를 추상 클래스로 선언하지 않는 이유는 모든 클래스에서 무조건 오버라이드할 필요 없이 그대로 사용할 수 있는 메소드를 구현해놓은 코드가 들어있기 때문이다.
Object 유형의 객체를 만들 수는 있지만, 실제로 그렇게 할 일은 별로 없다.
Object 클래스의 두가지 용도
1. 임의 클래스에 대해 어떤 작업을 하는 메소드를 만들 때 다형적 유형으로 사용하는 경우
2. 자바에 있는 모든 객체에서 실행 중에 필요한 진짜 메소드 코드를 제공하기 위해서

Object 유형의 다형적 레퍼런스를 쓸 때 나오는 객체(ArrayList<Object>)는 실제 객체의 유형이나 목록에 객체를 추가했을 때의 레퍼런스 유형하고는 상관 없이 무조건 Object 유형의 레퍼런스로 나오게 된다.

컴파일러에서 어떤 메소드를 호출할 수 있는지 결정할 때는 실제 객체 유형이 아닌 레퍼런스 유형을 기준으로 따진다.

ArrayList<Object>에 객체를 집어넣으면 그 객체는 원래의 유형과는 무관하게 Object로만 처리할 수 있다.
ArrayList<Object>로부터 레퍼런스를 받아오면 그 레퍼런스는 항상 Object유형입니다.

어떤 객체인지 모를 경우에는 instanceOf 연산자를 써서 확인한다.
if (d instanceOf Dog) {
    Dog d=(Dog) o;
컴파일러에서는 레퍼런스가 참조하는 실제 객체의 클래스가 아닌 레퍼런스 변수를 선언할 때 지정한 유형의 클래스를 확인하다.

인터페이스(interface)
인터페이스를 정의하려면 public interface Pet {...} <- class 대신에 interface 키워드를 사용한다.
인터페이스를 구현하려면 public class Dog extends Canine implements Pet {...} <- implements 뒤에 인터페이스 명을 지정해줍니다.

public interface Pet {
    public abstract void beFriendly() ;   <- 인터페이스에 들어있는 모든 메소드는 추상메소드므로 반드시
    public abstract void play() ;            <- 세미콜론으로 끝나야합니다.
}

서로 다른 상속 트리에 속한 클래스에서도 같은 인터페이스를 구현할 수 있습니다.
또한 한 클래스에서 인터페이스 여러 개를 구현할 수도 있습니다.

어떤 클래스를 하위클래스로 만들지, 추상 클래스로 만들지, 아니면 인터페이스로 만들지를 어떻게 결정하는가!
클래스를 새로 만들려고 할 때 그 클래스가 (Object를 제외) 다른 어떤 유형에 대해서도 'A는 B다' 테스트를 통과할 수 없다면 그냥 클래스를 만듭니다.
어떤 클래스의 더 구체적인 버전을 만들고 어떤 메소드를 오버라이드하거나 새로운 행동을 추가해야 한다면 하위클래스를 만듭니다(클래스를 확장합니다.)
일련의 하위클래스에서 사용할 틀(template)을 정의하고 싶다면, 그리고 모든 하위클래스에서 사용할 구현코드가 조금이라도 있다면 추상 클래스를 사용합니다. 그리고 그 유형의 객체를 절대 만들 수 없게 하고 싶다면 그 클래스를 추상 클래스로 만듭니다.
상속 트리에서의 위치에 상관없이 어떤 클래스의 역할을 정의하고 싶다면 인터페이스를 사용하면 됩니다.

핵심정리
1. 클래스를 만들 때 인스턴스를 만들 수 없게 하고 싶다면 abstract 키워드를 사용해서 추상 클래스로 만든다.
2. 추상 클래스에는 추상 메소드와 추상 메소드가 아닌 메소드 모두를 집어넣을 수 있다.
3. 클래스에 추상 메소드가 하나라도 있으면 그 클래스는 추상 클래스로 지정해야 한다.
4. 추상 메소드에는 본체가 없으며 선언 부분은 세미콜론으로 끝납니다(중괄호를 쓰지 않는다)
5. 상속 트리에서 처음으로 나오는 구상 클래스에서는 반드시 모든 추상 메소들르 구현해야 합니다
6. 자바에 들어있는 모든 클래스는 직접 또는 간접적으로 Object(java.lang.Object)의 하위클래스입니다.
7. 메소드를 선언할 때 인자, 리턴 유형을 Object로 지정해도 됩니다.
8. 어떤 객체에 대해서 메소드를 호출하려면 그 객체를 참조하는 레퍼런스 변수 유형의 클래스(또는 인터페이스)에 그 메소드가 있어야만 합니다. 객체의 실제 유형하고는 무관합니다. 따라서 Object 유형의 레퍼런스 변수로는 Object 클래스에 정의되어 있는 메소드만 호출할 수 있습니다. (레퍼런스가 참조하는 객체의 유형과는 무관)
9. Object 유형의 레퍼런스 변수는 캐스팅을 하지 않고는 다른 유형의 레퍼런스에 대입할 수 없습니다. 한 유형의 레퍼런스 변수를 하위 유형의 레퍼런스 변수에 대입하고 싶다면 캐스팅을 이용할 수 있습니다. 하지만 힙에 들어있는 객체가 캐스팅 호환 가능한 유형이 아니라면 실행 중에 캐스팅에 실패할 수도 있습니다.
10. 자바에서는 다중 상속을 허용하지 않습니다. 클래스는 단 하나만 확장할 수 있습니다. (직속 상위 클래스는 하나밖에 없다)
11. 인터페이스는 100% 추상 클래스이고 추상 메소드만 정의한다 정의는 interface로 하고 구현은 implements로 한다.
12. 클래스는 여러 개의 인터페이스를 구현할 수 있다.
13. 인터페이스의 모든 메소드는 자동으로 public 메소드, 그리고 abstract 메소드가 되기 때문에 인터페이스를 구현하는 클래스에서는 인터페이스에 들어있는 모든 메소드를 구현해야 한다.
14. 하위클래스에서 어떤 메소드를 오버라이드했는데, 상위클래스 버전을 호출하고 싶다면 super라는 키워드를 사용하면 된다. ex : super.runReport();
2008. 7. 22. 23:20

chapter 7. 상속과 다형성

상속이란 하위클래스가 상위클래스의 멤버(인스턴스 변수, 메소드)를 물려 받는 것
하위클래스는 별도의 메소드와 인스턴스 변수를 추가할 수 있고 상위클래스의 메소드를 오버라이드 할 수 있다.
그러나 인스턴스 변수는 오버라이드 하지 않는다. 인스턴스 변수가 특별한 행동을 정의하지 않기 때문에...

상속 트리의 설계
1. 공통적인 속성과 행동이 들어있는 객체를 찾는다.
2. 공통적인 상태와 행동을 나타내는 클래스를 설계한다.
3. 특정 하위클래스 유형에만 적용되는 행동(메소드 구현)이 필요한지 결정한다.
4. 공통적인 행동이 필요한 하위클래스를 두 개 이상 찾아서 추상화의 개념을 더 폭넓게 활용할 수 있는지 찾는다.
5. 클래스 계층 구조를 완성한다.

하위클래스에서 상위클래스에 있는 버전의 메소드와 새로 오버라이드한 버전의 메소드 둘 다 사용하고 싶을 경우에는 먼저 상속 받은 메소드를 실행시킨 후, 하위클래스에서만 실행할 메소드를 처리한다.

상속을 활용하여 설계할 때의 주의점
어떤 클래스가 다른 클래스(상위클래스)를 더 구체화한 형식이라면 상속을 활용한다.
같은 일반적인 형식에 속하는 여러 클래스에서 공유해야 하는 어떤 행동(구현된 코드)이 있다면 상속을 활용한다.
그러나 객체지향 프로그래밍에 있어서 상속의 핵심 기능 가운데 하나지만 행동을 재사용하는 데 있어 무조건 최선의 방법이 아니라는 점을 주의해야 한다.
상위클래스와 하위클래스 사이의 관계가 위에 있는  두 가지 규칙에 위배된다면 어떤 코드를 다른 클래스에서
재사용할 수 있다는 이유만으로 상속을 사용하면 안된다.
하위클래스와 상위클래스 사이에서 'A는 B이다' 관계가 성립하지 않는다면 상속을 사용하지 않는다.

상속의 주요 특징
자바에서는 하위클래스가 상위클래스를 확장한다고 한다.
하위클래스는 상위클래스에 있는 모든 public으로 지정한 인스턴스 변수와 메소드를 상속하지만 private로 지정한 인스턴스 변수와 메소드는 상속하지 않는다.
메소드는 오버라이드 가능하지만 인스턴스 변수는 오버라이드 하지 않는다.
'A는 B이다' 라는 관계를 확인한다.(한방향으로만 작동한다.)
하위클래스에서 메소드를 오버라이드하고 하위클래스의 인스턴스 변수에 대해 그 메소드를 호출하면 오버라이드된 버전의 메소드가 호출된다.(맨 밑에 있는것이 호출 됨)
B라는 클래스가 A라는 클래스를 확장하고 C는 B를 확장한다면 클래스 B는 클래스 A이고 클래스 C는 클래스 B이고 클래스 C는 클래스 A이다.

상속의 장점
코드가 중복되는 것을 방지할 수 있다.
(그러나 특정 메소드의 인자나 리턴 형식, 메소드명과 같이 상위클래스에 있는 것 가운데 하위클래스에서 반드시 필요로 하는 것을 변경하면 심각한 문제가 생길 수 있다.)
일련의 클래스를 위한 공통적인 규약(protocol)를 정의할 수 있다.

다형성의 이해
객체 선언과 대입의 세 가지 단계
Dog myDog = new Dog();
    1.          2.      3.
1. 레퍼런스 변수를 선언한다.
2. 객체를 만든다.
3. 객체와 레퍼런스를 연결한다.
주요사항은 레퍼런스 유형과 객체 유형이 똑같아야 한다는 점이다.

하지만 다형성을 활용하면 레퍼런스와 객체가 다른 유형이어도 됩니다.
Animal myDog = new Dog();

다형성을 사용하면 레퍼런스 유형을 실제 객체 유형의 상위클래스 유형으로 지정할 수 있다.

Animal[] animals = new Animal[5];
animals [0] = new Dog();
animals [1] = new Cat();
animals [2] = new Wolf();
animals [3] = new Hippo();
animals [4] = new Lion();

for (int i = 0 ; i < animals.length ; i++ ) {
  animals[i].eat();
  animals[i].roam();

의문사항
인자와 리턴 유형에 대해서도 다형성을 적용할 수 있다.


대부분의 상속계층은 넓지만 깊지는 않다.
클래스의 소스코드를 직접 접근할 수 없지만 어떤 클래스의 메소드가 작동하는 방식을 바꾸고 싶을 때는 클래스를 확장한 후 메소드를 오버라이드해서 더 나은 코드를 만들면 된다.
상속이 안되는 경우.
클래스를 private로 지정할 경우
클래스를 final로 지정할 경우
내부 클래스(inner class)인 경우
오버라이드할 수 없도록 만들고 싶은 경우에는 final 클래스를 사용한다.

상위클래스의 메소드를 오버라이드할 때이 규칙
1. 인자는 동일해야 하고, 리턴 유형은 호환 가능해야 한다.
2. 메소드를 더 접근하기 어렵게 만들면 안된다(public 메소드를 오버라이드해서 private 메소드를 만드는 등등)

메소드 오버로딩이란 이름이 같고 인자 목록이 다른 메소드 두 개를 만드는 것.
오버로딩을 활용하면 호출하는 쪽의 편의를 위해 같은 메소드를 서로 다른 인자 목록을 가진 여러 버전으로 만들 수 있다. 오버로드하는 메소드에서는 상위클래스에서 정의한 다형성 계약을 이행하지 않아도 되기 때문에 메소드 오버로딩은 훨씬 더 융통성이 좋다고 할 수 있다.
규칙 & 장점
1. 리턴 유형이 달라도 된다.
2. 리턴 유형만 바꿀 수는 없다.(인자 목록을 반드시 변경해야 한다.)
3. 접근 단계를 마음대로 바꿀 수 있다.