상속 Inheritance
- SuperClass = 상위 클래스 = 부모 클래스
- SubClass = 하위 클래스 = 자식 클래스
다른 클래스에서 이미 사용중인 코드를 재사용할 때 굉장히 유용하다. 단, B(하위) Is a A(상위) 관계일때만 사용하는 걸 추천한다.
sub클래스에서 super 클래스의 값들에 접근하려면 super를 쓰면 된다. 예를 들어 super.getEmail()
모든 클래스 = Object클래스의 하위 클래스
아무것도 상속받지 않는 클래스도 사실 객체를 상속받고 있다.
Person person = new Person( );
이렇게 클래스 인스턴스를 생성해보자.
그다음 행에 Person을 입력한 후 .
을 입력하자마자 Person 클래스에 없는 메소드(notify, hashCode, toString 등)도 보이는 걸 확인할 수 있다. 이는 자바의 기능 중 하나다. 아무것도 상속받지 않은 경우라도 모든 클래스가 Object.class를 상속받는다.
public class Person extends Object {
}
위와 같이 입력하고 이클립스에서 ctrl 누른 상태로 Object를 클릭하면 Object.class
파일이 뜨는 걸 볼 수 있다. 이 오브젝트가 클래스 체계의 근본이다.
object.class 파일의 주석에서 확인할 수 있는 내용은 아래와 같다
Class {@code Object} is the root of the class hierarchy.
Every class has {@code Object} as a superclass. All objects, includeing arrays, implement the methods of this class.
클래스가 다른 클래스에서 아무것도 상속받지 않으면, 디폴트값으로 객체의 값을 상속받는다고 자동으로 설정된다. 이것이 toString 같은 기본 메소드를 실행시킬 수 있는 이유이다.
이처럼 모든 클래스는 기본적으로 Object Class의 연장선임을 알 수 있다.
Method Overriding
위처럼 Object.class에 의해 상속되는 메서드를 덮어씌울수도 있다. 이는 Object class뿐만 아니라 모든 상속되는 메서드에 해당한다.
예를 들어 아래와 같은 코드가 있을 때, System.out.print(인스턴스명)의 결과로 패키지 이름을 포함한 클래스 이름, @ 기호, 해시코드가 출력된다. 이 때 출력되는 메서드가 Object.class에 정의되어있는 toString 메서드다.
public class Person {
// Person 클래스 정의
}
public class PersonRunner {
public static void main(String[] args) {
Person employee = new Person(); // Person 클래스의 인스턴스 생성. 생성자 Person 호출
System.out.println(employee); //employee 인스턴스의 정보 출력
}
}
다음의 Object.class 파일의 내용을 보면 왜 그런 결과가 출력되는지 알 수 있다.
// Object.class 파일의 내용 중 toString 부분
public String toString() {
return getClass().getName() + "@" + Interger.toHexString(hashCode())
}
Object.class에 패키지 이름을 포함한 클래스명, @기호, 해시코드가 출력되도록 toString 메서드의 내용이 정의되어있다. 이 toString 메서드의 내용을 클래스 내부에서 다른 내용으로 정의할 경우, 내가 정의한 그 내용으로 덮어씌워진다.
public class Person {
public String toString() { //toString 메서드 오버라이딩
return "@";
}
}
public class PersonRunner {
public static void main(String[] args) {
Person employee = new Person(); // Person 클래스의 인스턴스 생성. 생성자 Person 호출
System.out.println(employee); //@ 출력
}
}
예를 들어 위처럼 toString 메서드의 내용을 @출력으로 바꿔버리는 경우, System.out.print(employee)로 toString을 호출할 경우 내가 재정의한대로 @가 출력된다. 이것이 메서드 오버라이딩이다.
상속과 생성자
인스턴스 생성 시 생성자 출력 순서
public class Person { // Person 클래스
private String name;
private String email;
private String phoneNumber;
public Person() { // Person 생성자
System.out.println("나는 Person의 생성자다"); // Person Constructor출력
}
}
public class Employee extends Person { // Person 클래스를 상속받는 Employee 클래스
private String title;
private String employerName;
private char employeeGrade;
public Employee() { // Employee 생성자
System.out.println("나는 Employee의 생성자다"); // Employee Constructor
}
}
public class StudentRunner {
public static void main(String[] args) {
Employee employee = new Employee(); // Employee 클래스의 인스턴스 생성. Employee 호출
}
}
Employee employee = new Employee();
코드를 실행(Employee 클래스의 인스턴스 생성 → Employee 생성자 호출)한 경우에 출력되는 결과는 아래와 같다.
나는 Person의 생성자다
나는 Employee의 생성자다
Employee()
로 Employee생성자를 호출하면 Employee 생성자가 super()
를 자동으로 호출하는 것이다.
따라서 상위super클래스의 생성자가 먼저 출력된 뒤 하위sub클래스의 생성자가 출력된다.
즉, 하위클래스에서 생성자를 만들 때 그 안에 자동으로 상위클래스의 생성자 super();
가 세팅되는 것이다.
상위클래스 생성자에 매개변수가 존재하고 하위클래스 생성자에는 매개변수가 존재하지 않는 경우
public class Person { //Person 클래스
private String name;
private String email;
private String phoneNumber;
public Person(String name) { //Person 생성자 (매개변수 있음)
System.out.println("나는 매개변수를 받는 Person의 생성자다");
this.name = name;
}
}
public class Employee extends Person { //Person 클래스를 상속받는 Employee 클래스
private String title;
private String employerName;
private char employeeGrade;
public Employee() { // Employee 생성자 (매개변수 없음)
//super(); 사실 이렇게 상위super클래스의 생성자가 존재하는 것이나 다름없다
System.out.println("나는 Employee의 생성자다");
}
}
public class EmployeeRunner {
public static void main(String[] args) {
Employee employee = new Employee(); // 매개변수 없이 Employee 생성자 호출
}
}
위의 코드를 실행했을 때 아래와 같은 오류 문구가 나온다.
Implicit super constructor Person() is undefined.
Person의 생성자(Super constructor) 를 호출하고자 했으나 아무것도 없다는 뜻이다. 즉, 상위클래스의 생성자 Person()
가 존재하지 않는다는 뜻이다.
이런 경우에 해결을 위해 public으로 person() 생성자를 만들 수도 있다.
그러나 이름이 없는 인스턴스가 만들어지면 안되는 상황인 경우의 해결책은 아래와 같다.
public class Person {
private String name;
private String email;
private String phoneNumber;
public Person(String name) { //Person 생성자 (매개변수 있음)
System.out.println("나는 매개변수를 받는 Person의 생성자다");
this.name = name;
}
}
public class Employee extends Person {
private String title;
private String employerName;
private char employeeGrade;
public Employee(String name, String title) { //Employee 생성자 (매개변수 2개 있음)
super(name); //super클래스=Person클래스의 생성자를 name을 인자로 호출
this.title = title;
System.out.println("나는 Employee의 생성자다");
}
}
public class EmployeeRunner {
public static void main(String[] args) {
Employee employee = new Employee("jane", "programmer"); //인스턴스 생성. 인자 2개로 Employee 생성자 호출
}
}
- jane, programmer라는 인자 2개로 Employee 생성자를 호출한다.
- Employee 생성자의 super(name)을 실행한다. 원래 자동으로 실행되는 super()가 이걸로 대체되는 것이다.
- super(name) 즉 Person 생성자를 name 인자로 호출한다.
- "나는 매개변수를 인자로 받는 Person의 생성자다"가 출력되고 주체가 된 인스턴스 employee에 매개변수로 받아온 name을 입력한다.
- Person 생성자의 실행이 끝났으므로 Employee 생성자로 돌아온다. employee의 title에 매개변수로 받아온 title을 넣는다.
- "나는 Employee의 생성자다"를 출력한다.
- 종료.
상위super클래스의 생성자가 반드시 인자를 필요로 하는 경우, 이 생성자를 하위sub클래스에서 호출해야한다. 위의 코드 중 super(name);
이 그 예다.
여기서 알 수 있는 중요한 점은, 하위클래스의 생성자에서 상위클래스의 생성자를 정의해야 한다는 것이다.
Multiple levels of inheritance
class Animal { //Animal 클래스
}
class Pet extends Animal { //Animal을 상속받은 Pet
}
class Dog extends Pet { // Pet을 상속받은 Dog
}
상속관계는 다음과 같다.
Dog ← Pet ← Animal ← Object
이 경우, Dog 클래스에서도 toString()
메소드 호출이 가능한데, 이 toString은 Object 클래스에 있는 메소드이다.
상위클래스의 생성자는 하위클래스의 인스턴스가 될 수 없다
Pet pet = new Dog();
←가능
상위클래스가 하위클래스 생성자를 호출하며 인스턴스를 만드는 것은 가능하다.
Dog dog = new Pet();
←불가
하위클래스가 상위클래스 생성자를 호출하며 인스턴스를 만드는 것은 불가능하다.