토요일, 5월 18
Shadow

#023 상속

class Manager extends Employee
{
added methods and fields
}

다음은 Employee 클래스를 확장하여 만든 Manager 클래스에 대한 코드이다.
class Manager extends Employee
{ public Manager(String n, double s, Day d)
{ super(n, s, d);
secretaryName = “”;
}
public void raiseSalary(double byPercent)
{ // add 1/2% bonus for every year of service
Day today = new Day();
double bonus = 0.5 * (today.getYear() – hireYear());
super.raiseSalary(byPercent + bonus);
}
public String getSecretaryName()
{ return secretaryName;
}
public void setSecretaryName(String name)
{ secretaryName = name;
}
private String secretaryName;
}
키워드 extends 는 기존의 클래스로부터 파생되는 새로운 클래스를 생성한다는 것을 가리킨
다. 기존의 클래스를 상위 클래스(superclass), 기반 클래스(base class), 또는 부모 클래스(parent
class)라고 한다. 새로운 클래스를 하위 클래스(subclass), 파생 클래스(derived class), 또는 자식클래스(child class)라고 한다. 우리는 “상속” 개념에 적당한 부모/자식 비유를 더 좋아하지만, 자바 프로그래머들이 일반적으로 사용하는 용어는 상위 클래스와 하위 클래스이다.

public Manager(String n, double s, Day d)
{ super(n, s, d);
secretaryName = “”;
}
키워드 super 은 항상 상위 클래스를 가리킨다.(이 경우, Employee) 따라서 다음 라인은
super(n, s, d);
n, s, d 를 매개변수로 하여 Employee 클래스의 생성자를 호출하라는 뜻이다. 이 라인의 목적
은 하위 클래스의 모든 생성자는 상위 클래스의 데이터 필드를 생성하는 책임이 있기 때문이다.
만약 하위 클래스 생성자가 상위클래스의 생성자를 명시적으로 호출하지 않을 경우 상위 클래
스는 그 자신의 디폴트 생성자를 사용한다. 그런데 상위 클래스에 디폴트 생성자가 없고 하위
클래스가 상위 클래스의 어떠한 생성자도 호출하지 않는다면 자바 컴파일러는 에러 메시지를
띄운다. 상위 클래스의 하위 클래스의 생성자가 상위 클래스의 기본 생성자에 적절하지 않다면,
적당한 매개변수를 가지고 super 를 의도적으로 이용하여야 한다. super 호출은 하위 클래스
의 생성자 첫 라인에 위치하여야 한다.

일반적으로 반대의 상황은 성립하지 않는다. 상위 클래스 객체는 하위 클래스 객체에 대입될
수 없다. 예를 들면, 다음은 틀린 대입이 된다.
boss = staff[I]; // Error
이유는 명백하다. 하위 클래스 객체는 상위 클래스 객체보다 더 많은 필드를 가질 수 있다.(이
경우 실제로 그렇다.) 따라서 하위 클래스 메소드는 이 필드를 접근할 수 있어야 한다. 필드를
접근할 수 없다면, 실행시간 오류가 발생할 것이다. 상속을 하게 되면 필드를 추가할 수 있으므
로 ,하위 클래스 객체는 상위 클래스의 객체보다 더 많은 데이터 항목을 가질 수 있다는 사실
에 유의하여라.

예를 들면, Manager 객체에 raiseSalary 메시지를 보내면, Employee 클래스의
raiseSalary 메소드 대신에 Manager 클래스의 raiseSalary 메소드가 호출된다.
———————————————————————
노트 : 일반적으로 메소드의 이름과 매개변수 목록을 메소드의 사인(signature)이라 부른다. 예
를 들면, raiseSalary(double)과 raiseSalary(boolean)은 다른 사인을 가진 메소드이
다. 자바에서는 하나의 클래스 내에서, 또는 상위클래스와 하위클래스에서 사인은 같고 반환형
은 다른 메소드에 대해 컴파일 오류를 발생시킨다. 예를 들면, Employee 클래스에 void
raiseSalary(double) 메소드와 Manager 클래스에 int raiseSalary(double) 메소드
를 가질 수 없다.
———————————————————————

상속 계층 상의 객체의 위치에 따라 어느 메소드를 호출할 것인지 결정하는 객체의 능력을 보
통 다형성(polymorphism)이라 한다. 다형성의 아이디어는 같은 메시지에 대하여 객체가 다르게
응답할 수 있다는 점이다. 다형성은 상위 클래스로부터 상속 받는 어떤 메소드에도 적용 가능
하다.
다형성을 가능케 하는 주된 요인은 지연 바인딩(late binding)이라고 하는 것이다. 이것은 컴파일
러가 컴파일 시간에 메소드를 호출하는 코드를 생성시키지 않음을 의미한다. 대신, 객체에 메소
드를 정의할 때, 컴파일러는 객체의 타입 정보를 이용하여 어느 메소드를 호출할 것인지 계산
하는 코드를 만들어 낸다. 이 과정을 보통 지연 바인딩, 동적 바인딩(dynamic binding), 또는 동
적 디스패치(dynamic dispatch)이라고 부른다. 일반적인 함수 호출 방법은 정적 바인딩(static
binding)이다. 여기서는 어느 연산을 실행시킬 지가 컴파일 시간에 완전히 결정된다. 정적 바인
딩은 메소드 자체에만 관련 있고, 동적 바인딩은 객체 변수의 형과 상속 계층 상의 객체 위치
에 연관되어 있다.

상속의 금지: 최종(Final) 클래스
때로는 다른 사람이 여러분의 클래스로부터 다른 클래스를 파생시키지 못하도록 하고 싶을 때
가 있다. 부모 클래스가 될 수 없는 클래스를 최종(final) 클래스라고 부르며, 클래스의 정의에
이것을 가리키기 위해 final 수식어를 이용한다. 예를 들면, 이전 장의 Card 클래스는 최종
클래스이며, 이것의 헤더는 다음과 같이 시작된다.
final class Card
{ . . .
}

final 로 만드는 이유는 다음 두 가지 중의 하나이다.
1. 효율성
동적 바인딩은 정적 바인딩보다 비효율적이다. 따라서 가상 메소드는 더 느리게 실행된다. 동적
디스패치 방식은 직접적인 프로시저 호출보다 약간 덜 효과적이다. 더 중요한 사실은 파생된
클래스는 코드를 겹쳐(override) 쓸 수 있기 때문에 간단한 메소드라도 인라인 코드로 대치할 수
없다는 사실이다. 컴파일러는 최종 메소드를 인라인으로 만들 수 있다. 예를 들면,
e.getName()이 최종 메소드라면, 컴파일러는 이것을 e.name 로 대치할 수 있다.(이렇게 하여
캡슐화를 깨뜨리지 않고 인스턴스 필드를 직접 접근할 수 있다.)
프로시저 호출은 마이크로 프로세서가 현재 명령어를 처리하면서 다음 명령어를 미리 얻어서
처리하는 방식을 방해한다. 따라서 효율이 떨어진다. 호출을 대신하여 인라인 코드로 만든다면
커다란 효과를 얻을 수 있다. 이것은 현재 버전의 자바 바이트코드 인터프리터보다는 진정한
컴파일러에게 있어서 더욱 중요하다.
2. 안전성
동적 디스패치 방식의 유연성은 메소드를 호출할 때 무슨 일이 발생할지 조정할 수 없다는 것
을 의미한다. e.getName() 같은 메시지를 보낼 때, e 가 전혀 다른 문자열을 반환하는
getName 메소드를 재정의한 파생된 클래스의 객체일 가능성도 있다. 메소드를 final 로 만듦
으로써 이러한 모호성을 없앨 수 있다.

케스팅에 대해 특수한 개념을 가지고 있다. 예를 들면
double x = 3.405;
int nx = (int) x;
의 경우에는 x 를 정수로 바꾸어 소수분분을 없애 버린다.
정수를 실수로 변환할 필요가 종종 생기듯이, 하나의 클래스로부터 다른 클래스로 객체를 변환
할 필요도 생긴다. 기본형을 변환하는 경우처럼 이것을 캐스팅(casting)이라 부른다. 실제로 이
런 캐스팅을 하기 위해서 기본형의 변수 사이에 캐스팅을 사용하는 것과 비슷한 구문을 사용하
여라. 목적하고자 하는 타입을 괄호로 둘러싸고, 캐스팅하고자 원하는 객체 앞에 위치시켜라.
예를 들면, 아래와 같다:
Manager boss = (Manager)staff[0];
캐스팅을 하는 유일한 이유는 객체의 실제 타입을 격하시켜서(하위 클래스로 만들어서) 이것의
용량 전부를 사용하는 것이다. 예를 들면, Manager 클래스에서 staff 배열의 일부 항목은 일
반 고용인이므로, 이것은 Employee 객체의 배열이 되어야만 한다. Manager 클래스의 새로운
필드를 접근하려면 배열의 항목을 Manager 로 되돌려야 한다.(첫번째 절의 예제 코드에서는
캐스팅을 피하기 위해 특별한 노력을 기울였다. boss 변수를 배열에 저장하기 전에 Manager
객체로 초기화했다. 관리자의 비서를 찾으려면 적절한 타입이 필요하다.)

Manager boss = (Manager)staff[1]; // Error
프로그램이 실행되다가 자바는 부적당한 예상을 감지하고 예외를 발생시키고(11 장과 이 장의
측면 바(bar)를 보아라), 프로그램은 보통 종료된다. 캐스팅하기 전에 여러분의 객체가 다른 객
체의 인스턴스인지 확인하는 프로그래밍 습관이 좋다. 이것은 instanceof 연산자를 이용해서
실현가능하다. 예를 들면,
if (staff[1] instanceof Manager)
{ boss = (Manager)staff[1];
. . .
}
마지막으로 언급할 점은, 컴파일러는 캐스트가 아예 성공하지 못할 것이면, 캐스트를 하지 못하
게 한다. 예를 들면, 다음 캐스트는
Window w = (Window)staff[1];
Window 는 Employee 의 하위 클래스가 아니기 때문에 성공하지 못할 것이다.
요약하면,
1 상속 계층 내에서만 캐스트가 가능하다.
2 부모 클래스로부터 자식 클래스로 캐스팅하기 전에 instanceof 을 사용하여 계층을 검
사해 보아라.
캐스트를 하는 유일한 이유는 getSecretaryName 같은 관리자에게만 유일한 메소드를 사용
하기 위함이다. Employee 형의 객체에 대해 비서의 이름을 얻는 것이 중요하다면, 클래스를
다시 설계하고 공 문자열을 반환하는 getSecretaryName 메소드를 추가해야 한다. 어느 배
열의 위치에 어느 타입이 저장되었는지를 기억하고 형 질문을 수행하는 것보다는 이것이 더 의
미가 있다. 잘못된 캐스트는 프로그램을 종료시킨다는 사실을 기억하여라. 일반적으로
instanceof 연산자를 최소로 사용하는 것이 좋다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.