상속

상속을 이용하면 계층적으로 클래스를 만들 수 있다. 수퍼 클래스는 자신의 구현 내용을 서브 클래스에 물려주고, 서브 클래스는 수퍼 클래스로부터 구현 내용을 상속받는다.

  • 수퍼(super) 클래스 : 구현 내용을 서브 클래스에 물려주는 클래스, 부모 클래스라고도 한다.
  • 서브(sub) 클래스 : 수퍼 클래스로부터 구현 내용을 상속받는 클래스, 자식 클래스라고도 한다.

객체지향 프로그래밍에서 재사용 여부는 중요하다. 재사용은 클래스를 그대로 재사용하기도 하고 수퍼 클래스를 재사용하기도 한다.

상속 관계를 아닌 두 클래스에서 상속 예제로 확장하겠다. 다음은 상속 관계가 아닌 사원(Employee)클래스와 관리자(Manager)클래스다.

사원 클래스(Employee.java)
package net.java_school.example;

public class Employee {
	private String name;
	private String position;
	private String telephone;
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPosition() {
		return position;
	}

	public void setPosition(String position) {
		this.position = position;
	}

	public String getTelephone() {
		return telephone;
	}

	public void setTelephone(String telephone) {
		this.telephone = telephone;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(name);
		sb.append("|");
		sb.append(position);
		sb.append("|");
		sb.append(telephone);
		
		return sb.toString();
	}
	
}
관리자 클래스(Manager.java)
package net.java_school.example;

public class Manager {
	private String name;
	private String position;
	private String telephone;
	private String manageJob;
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPosition() {
		return position;
	}

	public void setPosition(String position) {
		this.position = position;
	}

	public String getTelephone() {
		return telephone;
	}

	public void setTelephone(String telephone) {
		this.telephone = telephone;
	}

	public String getManageJob() {
		return manageJob;
	}

	public void setManageJob(String manageJob) {
		this.manageJob = manageJob;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(name);
		sb.append("|");
		sb.append(position);
		sb.append("|");
		sb.append(telephone);
		sb.append("|");
		sb.append(manageJob);
		
		return sb.toString();
	}
	
}

Test 클래스를 만든다.

Test.java
package net.java_school.example;

public class Test {
	public static void main(String[] args) {
		Employee im = new Employee();
		im.setName("임꺽정");
		im.setPosition("대리");
		im.setTelephone("19");
		System.out.println(im.toString());	
	
		Manager hong = new Manager();
		hong.setName("홍길동");
		hong.setPosition("과장");
		hong.setTelephone("9");
		hong.setManageJob("프로젝트관리");
		System.out.println(hong.toString());
	}	
}

위의 두 클래스는 서로 아무런 관계가 없다. 하지만 '관리자는 사원이다'라는 명제가 옳으므로 두 클래스는 "~이다" 관계가 성립하는 상속 관계다. 이때 사원이 관리자보다 더 넓은 개념이므로 사원이 수퍼 클래스, 관리자가 서브 클래스가 된다.

상속 관계를 코드로 구현하기 위해 사원 클래스와 관리자 클래스에서 중복되는 코드를 확인해야 한다. name, position, telephone 인스턴스 변수와 이 변수에 대한 게터와 세터 메소드가 중복된다. 사원 클래스를 상속하도록 관리자 클래스를 아래와 같이 수정한다. 중복되는 코드는 상속되므로 관리자 클래스 소스에서 삭제한다.

Manager.java
package net.java_school.example;

public class Manager extends Employee {
	private String manageJob;
	
	public String getManageJob() {
		return manageJob;
	}

	public void setManageJob(String manageJob) {
		this.manageJob = manageJob;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(getName());
		sb.append("|");
		sb.append(getPosition());
		sb.append("|");
		sb.append(getTelephone());
		sb.append("|");
		sb.append(manageJob);
		
		return sb.toString();
	}
	
}

클래스가 어떤 클래스를 상속하려면 클래스 선언에 extends 키워드를 사용해야 한다. 이제 관리자 클래스는 사원 클래스 멤버를 마치 '자기 멤버1인 양' 쓸 수 있다. 상속된 부모의 멤버가 자식 멤버로 변하는 게 아니다. 상속된 멤버는 여전히 부모의 접근자가 적용된다. 관리자 클래스의 toString() 메소드에서 getName(), getPosition(), getTelephone()을 사용해야 하는 이유가 사원 클래스의 name, position, telephone의 접근자가 private이기 때문이다. 부모의 멤버변수인 name, position, telephone에 자식에서 바로 접근하려면 이들 멤버 변수의 접근자를 변경해야 하다. 사원 클래스와 관리자 클래스가 같은 패키지에 있으므로 사원 클래스의 name, position, telephone에 package private 접근자 이상으로 적용하면 된다. 만약 사원 클래스와 관리자 클래스가 같은 패키지가 아니라면 사원 클래스의 name, position, telephone의 접근자는 protected 접근자 이상으로 적용해야 한다. protected는 자식 타입의 객체에서 부모의 멤버에 접근할 때 부모 클래스가 다른 패키지에 속해 있다 하더라도 접근할 수 있게 한다. 다른 패키지에 있는 클래스간 상속관계를 보호한다는 의미에서 접근자 이름이 protected다. Test클래스를 실행하여 결과를 확인한다.

메소드 오버라이딩(Overriding)
부모로부터 상속받은 메소드 그대로 사용할 수 있지만, 원한다면 '재정의'해서 사용하는 것을 메소드 오버라이딩2이라고 한다. 자식 클래스에서 부모 클래스의 메소드를 재정의할 때 리턴 타입, 메소드명, 파라미터 리스트가 같아야 한다. 사원 클래스의 toString()은 Object 클래스의 toString()를 오버라이딩하고 관리자 클래스의 toString()은 사원 클래스의 toString()를 오버라이딩한다. 사원 클래스의 toString()이 Obejct 클래스의 toString()을 오버라이딩한다? 사원 클래스 코드(Employee.java)를 다시 확인해 보자. 클래스 선언에 extends 키워드가 있는가? 클래스 선언에 extends 키워드가 없다면 그 클래스는 Object 클래스를 상속하게 된다. 컴파일러가 개입하여 클래스 선언을 public class Employee extends Object로 변경하여 컴파일하기 때문이다. 결과적으로 사원 클래스의 부모 클래스는 Object가 된다. @Override와 같은 쓰임을 어노테이션이라 하는데, 자바 코드로 전달할 수 없는 정보를 컴파일러나 플랫폼에 전달한다. 위 코드에서 @Override는 메소드가 부모 클래스의 메소드를 오버라이딩한다는 정보를 컴파일러에게 알려주게 된다.

생성자(Constructor)

Test 클래스의 메인 메소드에서 Manager hong = new Manager();는 Manager 객체를 생성하는 코드다. 이제 이 코드에 대해서 좀 더 자세히 말할 때가 되었다. new 다음에 오는 Manager()는 메소드 모양이지만 메소드가 아닌 Manager()라는 생성자를 호출하는 코드다. 메소드와 마찬가지로 Manager() 생성자를 호출하려면, 클래스 몸체에 Manager() 생성자를 선언해야 한다. 그리고 new 다음에는 클래스에 선언된 생성자 중에 하나를 호출할 수 있다.

위의 예제에서는 사원 클래스와 관리자 클래스에 생성자를 만들지 않았다. 그렇지만 Test의 메인 메소드는 생성자를 호출한다. 그리고 에러 없이 결과를 볼 수 있다는 것은 호출한 생성자가 실행되었음을 의미한다. 만들지도 않는 생성자가 호출될 수 있을까? 답은 컴파일러에 있다. 클래스를 작성하면서 아무런 생성자도 만들지 않았다면, 컴파일러는 파라미터가 없는 생성자를 만들어 컴파일한다. 컴파일러가 자동으로 생성해 준 생성자를 '디폴트 생성자'(default constructor)라고 한다. 만일 생성자를 하나라도 만들었다면 컴파일러는 디폴트 생성자를 만들지 않는다.

다양한 파라미터 리스트를 가진 생성자를 여러개 만들 수 있다. 생성자는 '객체가 생성된 후' 바로 단 한번 호출되고 다시는 호출되지 않는다. '객체가 생성된 후'란 말에 주목해야 한다. 생성자라는 이름 때문에 생성자가 호출되어야 객체가 생성된다고 생각하면 오해다. new 키워드는 힙(heap) 메모리3에 객체를 위한 공간을 할당하고, 이때 인스턴스 변수의 값이 초기화4된다. 그다음 new 다음에 있는 생성자가 호출된다. 생성자가 에러없이 마치면 변수에 레퍼러스가 할당된다. Manager hong = new Manager();에서 hong에 할당된다. 만일 생성자가 제대로 종료되지 않았다면, 변수에 레퍼런스가 할당되지 않는다. 레퍼런스가 없는 객체는 사용할 수 없고 가베지 컬렉터의 수거 대상이다.

생성자는 객체가 생성 후 자동적으로 호출되므로 객체 초기화나 그 외 초기화와 관련된 작업만을 하도록 작성한다. 대부분 인스턴스 변수를 초기화하도록 생성자를 구현한다.

생성자는 리턴 타입이 없고, 이름은 클래스 이름과 같다. 많이 하는 실수 중 하나가 생성자 이름 앞에 void를 붙이는 경우다. void가 붙으면 메소드이지 생성자가 아니다.

서브 클래스는 수퍼 클래스의 멤버(인스턴스 변수와 메소드)를 상속받는다고 했다. 하지만 생성자는 상속되지 않는다. 그리고 자식 클래스 생성자 구현부의 첫 라인은 반드시 부모 클래스의 생성자를 호출하는 코드가 있어야 한다. 만약 없다면 컴파일러가 부모 클래스의 디폴트 생성자를 호출하는 코드를 첫 라인에 집어넣어 컴파일한다. 지금까지의 내용을 종합하여 사원 클래스와 관리자 클래스에 적절한 생성자를 추가해 보자. 생성자를 추가하려면 먼저 this와 super 키워드가 필요하다.

this
this가 실행되는 시점에 this는 객체 자신의 레퍼런스를 가진다. this 키워드는 생성자 안에서 다른 생성자를 호출할 때와 생성자나 메소드 몸체에서 인스턴스 변수와 파라미터를 구별하기 위해 사용한다. 참고로 이클립스에서 코드를 작성할 때 this 다음에 .(도트)를 이어 입력하면 사용가능한 자원(멤버 변수나 메소드 등)에 대한 코드 어시스트를 받을 수 있다.
super
super 키워드는 메소드 오버라이딩으로 숨겨진 부모 클래스의 메소드를 호출하거나 자식의 생성자에서 부모의 생성자를 호출할 때 사용한다.
사원 클래스에 생성자 추가
public Employee() {} //생성자를 만들 때 디폴트 생성자도 함께 만드는 습관을 들이자.

public Employee(String name, String position, String telephone) {
	this.name = name;
	this.position = position;
	this.telephone = telephone;
}
관리자 클래스에 생성자 추가
public Manager() {}

public Manager(String name, String position, String telephone, String manageJob) {
	super(name, position, telephone);
	this.manageJob = manageJob;
}
테스트의 메인 메소드 구현부 수정
Employee im = new Employee("임꺽정", "대리", "19");
System.out.println(im.toString());	

Manager hong = new Manager("홍길동", "과장", "9", "인사");
System.out.println(hong);

System.out.println(hong);에서 레퍼런스를 전달받는 println() 메소드는 레퍼런스가 가리키는 객체의 toString()메소드를 호출하여 반환된 문자열을 출력한다. 따라서 System.out.println(hong.toString());과 System.out.println(hong);의 결과는 같다. 위의 생성자 관련 코드는 컴파일러가 아래처럼 변경하여 컴파일한다. 어떤 코드를 더 좋아할지는 여러분 몫이다.

컴파일러가 개입하여 바꾼 사원 클래스의 생성자 코드
public Employee() {
	super();
}

public Employee(String name, String position, String telephone) {
	super();
	this.name = name;
	this.position = position;
	this.telephone = telephone;
}
컴파일러가 개입하여 바꾼 관리자 클래스의 생성자 코드
public Manager() {
	super();
}

public Manager(String name, String position, String telephone, String manageJob) {
	super(name, position, telephone);
	this.manageJob = manageJob;
}

컴파일러가 개입한 코드를 보면, 생성자를 만들 때 디폴트 생성자도 함께 만들면 좋은 이유를 깨닫게 된다.

부모 타입 변수에 자식 타입 레퍼런스를 할당할 수 있다.

다형성은 같은 모양이 다양한 형태로 실행되는 듯한 느낌이 들게 한다. 부모 타입 레퍼런스 변수에 자식 타입 레퍼런스를 할당하면 다형성이 생긴다.

다형성 그림
니체 extends 철학자
스피노자 extends 철학자
철학자 a = new 니체(); //a는 철학자 타입의 변수
a.말하다(); //니체 말하다.
a = new 스피노자();
a.말하다(); //스피노자 말하다.

a.말하다(); 코드로 니체가 말하고 스피노자가 말한다. a.말하다();는 다형성이 있다. a.말하다();가 실행되는 시점(Runtime)에 니체나 스피노자가 말할지가 결정된다. 컴파일시에 결정되는 않는다.

부모 클래스 타입 변수가 자식 타입의 객체를 참조할 때 객체의 모든 멤버에 접근할 수 있는 건 아니다. 제한이 있는데 자식의 고유한 멤버는 접근하지 못한다. 여기에도 예외가 있다. 자식이 오버라이딩한 메소드는 접근할 수 있다.(사실 이 부분이 가장 이해하기 힘들다.) 결론적으로, 부모 타입의 레퍼런스는 부모로부터 상속된 것과 오버라이딩 메소드를 접근할 수 있다. 예제를 통해 알아보자. 사원 클래스와 관리자 클래스는 그대로 두고 테스트의 메인 메소드에 다음을 추가한다.

Test.java 의 메인 메소드 구현부 추가
Object jang = new Manager("장길산", "부장", "1", "영업");
System.out.println(jang);
//jang.setManageJob("회계");//Object형 레퍼런스로 setManagerJob() 메소드에 접근할 수 없다.
//만약 장길산 사원객체를 완전하게 사용하기를 원한다면 레퍼런스 변수를 타입 캐스팅한다.
Manager janggilsan = (Manager)jang;
janggilsan.setManageJob("회계");
System.out.println(jang);

마지막 줄 System.out.println(jang); 여기서 jang은 Object 타입 레퍼런스다. println() 메소드 내부에서 이 레퍼런스가 가리키는 객체의 toString() 메소드를 호출한다. 실제로 힙 메모리에 생성된 객체는 관리자 객체이므로 관리자 클래스에서 오버라이딩한 toString() 메소드가 호출된다. 아래 그림에서 Manager, Employee, Object 타입 레퍼런스 모두 (2)와 (3) 메소드를 보지 못한다. 따라서 (1) 메소드가 호출된다. Manager객체 그림

메소드 오버로딩(method overloading)
파라미터 리스트가 다르면 똑같은 이름의 메소드를 얼마든지 만들 수 있다는 것이 메소드 오버로딩이다. overloading이란 영어 단어의 뜻은 '과적'이다. '고속도로에서 과적 차량 단속'이라 할 때 바로 그 과적이다. 메소드 오버로딩은 똑같은 이름으로 메소드를 여러 개 만들어도 컴파일 에러가 나지 않을 뿐 아니라 호출하면서 전달한 아규먼트에 따라 거기에 꼭 맞게 선언된 메소드가 호출되어야 한다. 메소드 반환값의 타입은 메소드 오버로딩과 아무런 상관이 없다. 파라미터 리스트가 같으면서 반환값 타입만 다르게 하여 같은 이름의 메소드를 만들 수 없다. 이런 메소드를 만들면 컴파일 에러를 만나게 된다.
자바에서는 작명이 중요하다. 메소드 이름만 보고도 메소드가 무슨 행위를 하는지 파악이 되도록 해야 한다. 메소드 오버로딩은 이름을 짓는데 있어서 부담을 줄여준다.
오버로딩된 메소드를 사용하는 입장에서는 같은 모양이 다양한 형태로 실행되는 듯한 느낌을 받게 된다. 메소드 오버로딩은 다형성을 가진다. 가장 좋은 예는 System.out.println();처럼 써왔던 print()와 println() 메소드5다. print()와 println() 메소드는 어떤 아규먼트라도 모두 출력하는 듯이 보인다. 사실은 print()와 println() 메소드가 파라미터 리스트가 다르게 오버로딩(과적)되어 만들어져 있고, 아규먼트에 따라 그에 맞는 메소드가 정확히 호출되는 것이다.

final 키워드

  1. 클래스 선언에 사용하면, 해당 클래스를 상속하여 서브 클래스를 만들지 못한다.6
  2. 메소드 선언에 사용하면, 해당 메소드는 자식 클래스에서 오버라이딩 할 수 없다.
  3. 자바에서 상수를 만들 때는 변수명 앞에 final을 붙인다.

추상클래스(Abstract Class)

클래스 선언에서 class앞에 abstract가 있는 클래스를 추상 클래스라고 한다. 추상 클래스는 일반적인 클래스와 달리 new 키워드로 객체로 생성할 수 없다. 추상 클래스를 이해하기 위해서는 먼저 추상 메소드의 의미를 알아야 한다. 추상 메소드란 메소드 선언만 있고 메소드의 몸체가 없는 메소드다. 추상 메소드는 다른 메소드와 구분하기 위해서 메소드 선언에서 class 앞에 abstract를 붙인다. 클래스에 추상 메소드가 하나라도 있다면, 그 클래스는 추상 클래스로 선언해야 한다. 반대로 모든 추상 클래스에 1개 이상의 추상 메소드가 있어야 하는 건 아니다. 필요에 따라서 추상 메소드없이 추상 클래스를 선언하기도 한다. 추상 클래스를 이용하려면 추상 클래스를 상속한 자식 클래스를 만들고 자식 클래스에서 추상 클래스의 추상 메소드를 구현해야 한다. 좋은 예제는 아니더라도 지금까지의 예제를 수정하여 추상 클래스 예제를 만들어 보겠다. 아래처럼 추상클래스 AbstractEmployee 클래스를 작성하자. 이와 같이 구체적이지 않고 모호하게 만들면 이식성이 좋아진다. 물론 설계가 잘되었다는 전제가 있어야 한다.

AbstractEmployee.java
package net.java_school.example;

public abstract class AbstractEmployee {
	private String name;
	
	public AbstractEmployee() {}
	
	public AbstractEmployee(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	//추상메소드
	public abstract void doWork();
	
	
}

사원 클래스가 AbstractEmployee 추상 클래스를 상속하도록 변경한다. 이때 사원 클래스는 AbstractEmployee클래스의 추상 메소드 doWork() 메소드를 구현해야 한다.

Employee.java
package net.java_school.example;

public class Employee extends AbstractEmployee {
	private String position;
	private String telephone;
	
	public Employee() {}
	
	public Employee(String name,String position, String telephone) {
		super(name);
		this.position = position;
		this.telephone = telephone;
	}
	
	public String getPosition() {
		return position;
	}
	public void setPosition(String position) {
		this.position = position;
	}
	public String getTelephone() {
		return telephone;
	}
	public void setTelephone(String telephone) {
		this.telephone = telephone;
	}
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append(this.getName());
		sb.append("|");
		sb.append(position);
		sb.append("|");
		sb.append(telephone);
		
		return sb.toString();
	}
	
	public void doWork() {
		System.out.println("일한다");
	}
		
}

관리자 클래스는 변경하지 않는다. 지금까지 작성한 Test클래스의 메인 메소드 첫 라인을 아래처럼 변경하여 실행한다. AbstractEmployee im = new Employee("임꺽정", "대리", "19");

인터페이스(interface): 컴포넌트7의 기능을 정의한다.

인터페이스는 클래스 선언에 class대신 interface를 쓰며 모든 메소드가 추상 메소드다. 모두 추상 메소드이기에 일반 메소드와 구분하기 위한 abstract 키워드는 생략한다. 인터페이스 몸체에 선언된 필드는 무조건 static final8이다.

추상 클래스와 마찬가지로 인터페이스 역시 객체로 만들 수 없다. 인터페이스를 구현한 클래스를 객체화해서 사용한다. 인터페이스를 구현한 클래스는 해당 인터페이스의 모든 추상 메소드를 빠짐없이 구현한 클래스다. 인터페이스를 구현한 클래스의 클래스 선언은 extends 대신 implements 키워드를 사용해 클래스가 어떤 인터페이스를 구현했는지를 정의한다. implements 키워드 뒤에는 ,(콤마)로 구분하여 하나 이상의 인터페이스가 올 수 있는데 마치 다중 상속처럼 보일 수 있다.

자바 인터페이스는 전자제품의 사용자 인터페이스와 같다. 대부분 TV는 화면 하단에 - 음량 + 과 - 채널 + 과 같은 인터페이스를 제공한다. 전자제품의 사용자 인터페이스가 같다면 사용법도 같다. 자바 클래스가 전자제품이라면 전자제품의 사용자 인터페이스는 자바 인터페이스다. 텔레비젼이 브라운관에서 PDP, LCD, LED로 구현 내용은 달라졌지만 다행히도 인터페이스는 변경되지 않았다. 그 결과 새로운 기술의 TV를 구입한 후 사용자 매뉴얼을 보지 않아도 TV를 사용하는데 문제가 없다.

언제 인터페이스를 사용해야 하나?

언제 상속을 사용하고 언제 인터페이스를 사용해야 하는지는 어려운 문제다. '~이다' 관계를 발견하면 상속을 고려한다. '~이다' 관계가 아니면서 '어떤 기능을 가져야 한다'에 초점이 맞춰지면 인터페이스를 고려한다. 인터페이스는 상속처럼 계층형 관계에 제약을 받지 않는다는 점과 자바는 단일 상속만을 지원하는데 반해 인터페이스는 다중상속과 같은 모양을 가질 수 있다는 점도 염두에 두자.

사원 클래스를 상속한 운전기사 클래스를 만들었다고 가정하자. 그리고 배달맨 클래스가 있는데 배달맨은 사원은 아니면서 운전기사 클래스와 같은 기능이 많다면 그 기능을 가지고 인터페이스를 만들 수 있다. 이때 배달맨 클래스와 운전기사 클래스에 있는 중복된 코드로 부모 클래스는 만들 수 없다. 왜냐하면 운전기사 클래스의 부모 클래스가 둘이 되기 때문이다. 자바 소스 차원에서 보면 클래스 선언에는 extends 다음에 단 하나의 클래스만 둘 수 있다. extends 다음에 ,(콤마)로 부모 클래스를 나열할 수 없다. 위에서 언급한 계층형 관계의 제약 중 하나다. 하지만 배달맨 클래스와 운전기사 클래스의 공통된 기능을 인터페이스로 만들 수 있다. 이제까지의 내용을 순서대로 실습해 보자. 먼저 운전기사 클래스를 아래와 같이 만든다. 운전기사는 직원이므로 사원 클래스를 상속한다.

운전기사 클래스(Driver.java)
package net.java_school.example;

public class Driver extends Employee {
	private String carNo;//관리하는 차넘버
	
	public Driver() {}
	
	public Driver(String name, String position, String telephone, String carNo) {
		super(name, position, telephone);
		this.carNo = carNo;
	}

	public String getCarNo() {
		return carNo;
	}

	public void setCarNo(String carNo) {
		this.carNo = carNo;
	}

	public void drive() {
		System.out.println(this.getName() + " 운전한다");
	}
	
	public void transport() {
		System.out.println(this.getName() + " 물건을 운송한다");
	}
	
}
배달맨 클래스(Transportor.java)
package net.java_school.example;

public class Transportor {
	private String carNo;
	
	public String getCarNo() {
		return carNo;
	}

	public void setCarNo(String carNo) {
		this.carNo = carNo;
	}

	public void drive() {
		System.out.println("운전한다");
	}
	
	public void transport() {
		System.out.println("물건을 운송한다");
	}
	
}

배달맨클래스와 운전기사 클래스에 drive()와 transport()이라는 공통 기능이 있다. 이 공통 기능을 인터페이스로 작성할 수 있다. 인터페이스 이름을 Drivable이라고 하자.

Drivable.java
package net.java_school.example;

public interface Drivable {
	public void drive();
	
	public void transport();

}

배달맨과 운전기사가 이 인터페이스를 구현하도록 수정한다.

Driver.java
package net.java_school.example;

public class Driver extends Employee implements Drivable {
	private String carNo;
	
	public Driver() {}
	
	public Driver(String name, String position, String telephone, String carNo) {
		super(name, position, telephone);
		this.carNo = carNo;
	}

	public String getCarNo() {
		return carNo;
	}

	public void setCarNo(String carNo) {
		this.carNo = carNo;
	}
	
	@Override
	public void drive() {
		System.out.println(this.getName() + " 운전한다");
	}
	
	@Override
	public void transport() {
		System.out.println(this.getName() + " 물건을 운송한다");
	}
	
}
Transportor.java
package net.java_school.example;

public class Transportor implements Drivable {
	private String carNo;
	
	public String getCarNo() {
		return carNo;
	}

	public void setCarNo(String carNo) {
		this.carNo = carNo;
	}

	@Override
	public void drive() {
		System.out.println("운전하다");
	}
	
	@Override
	public void transport() {
		System.out.println("물건을 운송하다");
	}
	
}

Test 클래스의 메인 메소드에 아래 코드를 추가한다.

Test.java
Drivable a = new Driver("슈마허","대리","ext:8","01거 5000");
System.out.println(a);
a.drive();
Drivable b = new Transportor();
// b.setCarNo("01거 7000"); // 컴파일 에러!
b.drive();

상속에서 확인했던 '부모 타입 레퍼런스 변수에 자식 타입 레퍼런스가 할당될 수 있다'와 같이 '인터페이스 타입의 변수에 인터페이스를 구현한 클래스의 레퍼런스가 할당될 수 있다.'

주석
  1. 인스턴스 변수와 메소드를 일반적으로 멤버(member)라고 한다.
  2. override라는 영어 단어의 숨은 뜻에 주목해야 한다. ride는 올라탄다는 의미인데 여기에 over가 붙으면 위가 아래를 완전히 덮어서 아래는 보이지 않는다는 뜻이 된다. 보이지는 않지만 덮여진 아래가 없어지지 않으므로, 컴퓨터 용어 overwrite와는 전혀 다르다.
  3. 자바에서 heap 메모리는 객체가 생성되는 공간이다.
  4. 생성자가 호출되기 전 객체의 인스턴스 변수 값은, 숫자 타입은 0으로, 불린은 false로, 레퍼런스 변수는 null로 초기화된다. char도 엄밀하게는 숫자 타입으로 유니코드 테이블에서 0에 해당하는 문자로 초기화된다.
  5. print()와 println() 메소드는 java.io.PrintStream 클래스의 메소드다.
  6. String 클래스는 final 클래스다. String 클래스를 상속하여 문자열 클래스를 만들 수 없다.
  7. 한가지 목적을 위한 여러 기능을 제공하기 위해 여러 요소들로 구성된 독립적 단위다. 조건으로는 이식성이 좋아야 한다. 필요한 컴포넌트를 구입해서 마치 레고 블럭처럼 시스템을 구축할 수 있다면 좋지 않겠는가? 인터페이스는 컴포넌트의 기능을 정의한다. 통합할 때 컴포넌트는 다른 컴포넌트의 인터페이스만을 보게 프로그래밍된다.
  8. static이 붙으면 객체에 속한 게 아니다. static이 붙은 변수는 인스턴스 변수가 아니며, static이 붙은 메소드는 객체 행위를 정의하는 메소드가 아니다. static에 관한 자세한 내용은 다음 장에서 다룬다.