상속(Inheritance)은 OOP(Object Oriented Programming)의 네 가지 원칙 중 하나이다.
class Employee {
private String name;
private int salary;
public Employee() {
this.name = "Noname";
this.salary = 0;
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public int getSalary() { return this.salary; }
public void setSalary(int salary) { this.salary = salary; }
}
위와 같은 클래스가 있다고 해 보자. 우리는 이 Employee
클래스를 확장한 클래스를 구현할 수 있다.
class Manager extends Employee {
private int bonus;
public void setBonus(int bonus) { this.bonus = bonus; }
}
Manager
클래스는 Employee
클래스의 모든 멤버(변수와 메서드)를 상속한다.Manager
클래스는 자신만의 클래스 정의에 정의된 새로운 멤버를 가진다.bonus
, setBonus()
Manager
클래스는 다음과 같이 쓸 수 있다.
Employee
클래스에 정의된 메서드(getName
, getSalary
)가 어떻게 Manager
클래스의 인스턴스에서 호출되는지 주의하자.
public class Lecture {
public static void main(String[] args) {
Manager m = new Manager();
System.out.println(m.getName() + " " + m.getSalary();
}
}
Employee
클래스는 Manager
클래스의 superclass다.
Manager
클래스는 Employee
클래스의 subclass다.
만약 Manager
클래스가 getName이나 getSalary 같은 메서드를 정의하고 있지 않다면, 그의 superclass(Employee
)에 있는 메서드가 사용된다.
Manager
클래스의 정의에 getSalary를 정의하는 것도 가능하다.
이 경우, Manaer
클래스의 getSalary 메서드는 Employee
클래스의 getSalary 메서드를 오버라이드(override)하고, 이 메서드가 사용된다.
이를 메서드 오버라이딩(Method Overriding)이라고 한다.
super
키워드는 superclass를 가리키는 지시자(directive)이다.class Manager extends Employee {
private int bonus;
public void setBonus(int bonus) { this.bonus = bonus; }
public int getSalary() {
return super.getSalary() + bonus;
}
super.getSalary()
를 호출하는 대신에 바로 인스턴스 변수에 접근할 수는 없을까?
salary
는 private으로 정의되어 있기 때문이다.class Employee {
private String name;
private int salary;
public Employee() {
this.name = "Noname";
this.salary = 0;
}
public String getName() { return this.name; }
public void setName(String name) { this.name = name; }
public int getSalary() { return this.salary; }
public void setSalary(int salary) { this.salary = salary; }
}
class Manager extends Employee {
private int bonus;
public void setBonus(int bonus) { this.bonus = bonus; }
public int getSalary() {
return this.salary + bonus // Error! salary is private in class Employee
}
만약 subclass의 메서드가 superclass의 메서드와 같은 이름을 가졌지만 다른 매개변수 타입을 가진다면 그 메서드는 오버라이딩 메서드가 아니다.
만약 프로그래머가 오버라이딩 메서드를 구현하길 원한다면@override
라는 어노테이션(annotation)을 사용할 수 있다. 이는 실수를 방지하게 해 준다.
class Employee {
private String name;
private int salary;
public Employee() {
this.name = "Noname";
}
public boolean worksFor(Employee supervisor) {
System.out.println("Employee.worksFor");
return (this.supervisor == supervisor);
}
}
class Manager extends Employee {
@Override // this will cause compile error
public boolean worksFor(Manager supervisor) {
System.out.println("Manager.worksFor");
return (this.supervisor == supervisor);
}
}
메서드를 오버라이드할 때, 리턴 타입을 subclass로 변경할 수 있다.
아래의 getSupervisor
메서드는 Employee
대신 Manager
를 반환함에도 오버라이딩 메서드이다.
class Employee {
private String name;
private int salary;
public Employee() {
this.name = "Noname";
}
public Employee getSupervisor() {
System.out.println("Employee");
return supervisor;
}
}
class Manager extends Employee {
@Override // this is valid
public Manager getSupervisor() {
System.out.println("Manager");
return (Manager)supervisor;
}
메서드를 오버라이드할 때, 오버라이딩 메서드는 최소한 superclass의 메서드와 같은 접근 가능성을 가져야 한다.
오버라이딩 메서드는 상속된 메서드의 visibility를 줄일 수 없다.
class Employee {
private String name;
private int salary;
public Employee() {
this.name = "Noname";
}
public Employee getSupervisor() {
System.out.println("Employee");
return supervisor;
}
}
class Manager extends Employee {
@Override // Error! the overriding method has less visibility
protected Manager getSupervisor() {
System.out.println("Manager");
return (Manager)supervisor;
}
subclass를 생성할 때, 그의 생성자는 그것의 superclass의 생성자를 호출한다.
Manager
클래스의 인스턴스를 생성할 때, 기본 생성자가 호출된다.class Employee {
private String name;
private int salary;
public Employee() {
name = "Noname";
salary = 50000;
}
public String getName() { return this.name; }
public int getSalary() { return this.salary; }
}
class Manager extends Employee {
private int bonus;
public void setBonus(int bonus) { this.bonus = bonus; }
public int getSalary() { return super.getSalary() + this.bonus; }
}
class Employee {
private String name;
private int salary;
public Employee() {
name = "Noname";
salary = 50000;
}
public void setName(String name) { this.name = name; }
public String getName() { return this.name; }
public int getSalary() { return this.salary; }
}
class Manager extends Employee {
private int bonus;
public Manager () {
super.setName("NoName(Manager)");
bonus = 1000;
}
public void setBonus(int bonus) { this.bonus = bonus; }
public int getSalary() { return super.getSalary() + this.bonus; }
}
super()
를 사용해서 명시적으로 superclass의 생성자를 호출할 수 있다.super()
문은 생성자의 첫 번째 문장이어야 한다. 그렇지 않으면 에러를 발생시킨다!class Employee {
private String name;
private int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return this.name; }
public int getSalary() { return this.salary; }
}
class Manager extends Employee {
private int bonus;
public Manager (String name, int salary) {
//super(name, salary); // Error! Employee does not have a constructor with no parameter.
bonus = 10000;
}
public void setBonus(int bonus) { this.bonus = bonus; }
public int getSalary() { return super.getSalary() + this.bonus; }
}
만약 클래스 정의가 생성자를 포함하지 않는다면, 암시적 생성자가 거기 있는 것으로 친다.
어떤 생성자가 정의되면, 암시적 생성자는 더 이상 사용되지 않는다.
class Employee {
private String name;
private int salary;
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
}
Employee e = new Employee(); //Error! no Employee() exists
subclass의 객체를 superclass 타입의 객체에 할당할 수 있다.
Manager boss = new Manager();
Employee emp1 = boss;
int salary = emp1.getSalary(); // call getSalary defined in both Employee and Manager.
만약 getSalary 메서드를 호출하면 Manager 클래스에 정의된 메서드가 호출된다.
boss
가 Employee 타입의 변수에 할당되었더라도, 그것은 Manager 클래스의 객체인 것이다. empl
은 Employee 타입이지만 가리키는 객체의 클래스(Manager)의 메서드를 호출하게 된다.
JVM은 객체의 클래스를 확인하고 맞는 메서드를 호출한다. 이를 Dynamic method lookup이라고 한다. Dynamic method lookup은 다음과 같은 것을 가능하게 해 준다. (Manager와 Janitor는 Employee 클래스의 subclass이다.)
Employee[] staff = new Employee[...];
staff[0] = new Employee(...);
staff[1] = new Manager(...);
staff[2] = new Janitor(...);
JVM이 런타임에 dynamic method lookup을 수행한다고 하더라도, 다음의 코드는 컴파일 에러를 발생시킨다.
Employee emp1 = new Manager("Donald", 100000);
emp1.setBonus(20000); // setBonus is only defined in class Manager
왜냐하면 컴파일러는 emp1.setBonus(20000);
문장을 프로세싱할 때 Employee 클래스에서 setBonus 메서드를 찾기 때문이다. 컴파일 에러를 피하고 싶다면, 클래스를 subclass로 변환해 줘야 한다.
Employee emp1 = new Manager("Donald", 100000);
if(emp1 instanceof Manager) {
Manager mgr = (Manager) emp1;
mgr.setBonus(20000);
}
System.out.println(emp1.getSalary());