상속이란 기존의 클래스에 기능을 추가하거나 재정의하여 새로운 클래스를 정의하는 것이다. 이러한 상속은 캡슐화, 추상화와 더불어 객체지향 프로그래밍을 구성하는 중요한 특징 중 하나이다.
상속을 이용하면 기존에 정의되어 있는 클래스의 모든 필드와 메소드를 물려받아, 새오누 클래스를 생성할 수 있다.
이때 기존에 정의되어 있던 클래스를 부모클래스(parent class) 또는 상위 클래스(super class), 기초 클래스(base class)라고도 한다.
그리고 상속을 통해 새롭게 작성되는 클래스를 자식(child class) 또는 하위 클래스(sub class), 파생 클래스(derived class)라고도 한다.
자식 클래스란 부모 클래스의 모든 특성을 물려받아 새롭게 작성된 클래스를 의미한다.
//문법
class 자식클래스이름 extend 부모클래스이름{...}
부모는 자식 클래스에 포함되어 있으므로 부모 클래스에 새로운 필드를 하나 추가하면, 자식 클래스에도 자동으로 해당 필드가 추가된 것처럼 동작한다.
자식 클래스에는 부모 클래스의 필드와 메소드만이 상속되며, 생성자와 초기화 블록은 상속되지 않는다. 또한, 부모 클래스의 접근제어가 private
나 default
로 설정된 멤버는 자식 클래스에서 상속받지만 접근할 수는 없다.
//예시
class Dog {
int teethCount; // 중복된 속성들
int legCount; // 중복된 속성들
int tailCount; // 중복된 속성들
void bark();
}
class Cat {
int teethCount; // 중복된 속성들
int legCount; // 중복된 속성들
int tailCount; // 중복된 속성들
void meow();
}
class Lion {
int teethCount; // 중복된 속성들
int legCount; // 중복된 속성들
int tailCount; // 중복된 속성들
void roar();
}
////
class Animal {
int teethCount;
int legCount;
int tailCount;
}
class Dog extends Animal { // 상속을 통해 중복 코드를 제거
void bark();
}
class Cat extends Animal { // 상속을 통해 중복 코드를 제거
void meow();
}
class Lion extends Animal { // 상속을 통해 중복 코드를 제거
void roar();
}
이렇게 상속을 통해 클래스를 구현하면 다음과 같은 장점을 얻을 수 있다.
1. 클래스 간의 관계 형성을 명시해줌으로써 코드의 가독성을 높일 수 있다.
2. 자주 사용하는 코드를 공통적으로 사용하여 불필요한 중복을 제거하고 일관성을 유지할 수 있다.
3. 공통으로 사용하는 코드만 수정하면 되므로 생산성을 높이고, 유지보수를 쉽게 만들어준다.
super 키워드는 부모 클래스로부터 상속받은 필드나 메소드를 자식 클래스에서 참조하는데 사용하는 참조변수이다.
인스턴스 변수의 이름과 지역 변수의 이름이 같을 경우 변수 앞에 this 키워드를 사용하여 구분할 수 있던 것처럼 부모 클래스의 멤버와 자식 클래스의 멤버 이름이 같을 경우 super키워드를 사용해 구별할 수 있다.
//예제
class Parent {
int a = 10; // 부모 인스턴스 변수
}
class Child extends Parent {
int a = 20; // 자식 인스턴스 변수
void display() {
System.out.println(a); // 자식 인스턴스 변수 a 출력
System.out.println(this.a); // 자식 인스턴스 변수 a 출력
System.out.println(super.a); // 부모 인스턴스 변수 a 출력
}
}
public class Inheritance02 {
public static void main(String[] args) {
Child ch = new Child();
ch.display();
/*
실행 결과 :
20
20
10
*/
}
}
같은 변수명이라도 this냐 super 키워드냐에 따라 호출하는 인스턴스 변수가 달라지게 된다.
this
메소드가 같은 클래스의 다른 생성자를 호출할 때 사용된다면, super()
메소드는 부모 클래스의 생성자를 호출할 때 사용된다.
자식 클래스의 인스턴스를 생성하면, 해당 인스턴스에는 자식 클래스의 고유 멤버뿐만 아니라 부모 클래스의 모든 멤버까지도 포함되어 있다. 따라서 부모 클래스의 멤버를 초기화하기 위해서는 자식 클래스의 생성자에서 부모 클래스의 생성자까지 호출해야 한다.
이러한 부모 클래스의 생성자 호출은 모든 클래스의 부모 클래스인 Object 클래스 생성자까지 계속 거슬러 올라가며 수행된다.
public class Employee {
private String name;
// 생성자를 직접 지정 → 디폴트 생성자는 작동치 않음
public Employee(String name) {
this.name = name;
}
}
public class Developer extends Employee {
private double salary;
public Developer(String name) {
super(name); // 자식 생성자의 입력값 name을 받아 부모 생성자 public Employee(String name) 를 호출
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getSalary() {
return salary;
}
}
정리하자면 부모 클래스와 자식 클래스가 서로 상속 관계를 맺고 new 키워드를 통해 자식 클래스를 초기화 할때, 반드시 자식 클래스 생성자 내에서 부모 클래스 생성자를 호출하는 메서드 super() 가 가장 먼저(첫번째 행에서) 실행 된다.
이는 원래 직접 명시해야 되지만 디폴트 생성자일 경우 개발 편의성을 위해 생략할 수 있도록 되어 있다. (오히려 편의성 때문에 햇깔리기 마련이다)
만일 직접 생성자를 정의하거나 생성자 오버라이딩이 되어 있을 경우 메서드 시그니처에 맞는 생성자를 super(name) 메서드로 직접 정의해서 호출해야 된다. (생략된 super() 는 디폴트 생성자만 호출하기 때문이다)
this() 를 이용해 다채로운 자기자신 생성자 호출을 할수 있었던 것 처럼, super() 를 이용해 다채로운 부모 생성자 호출이 가능해진다.
오버로딩(overloading)이란 서로 다른 시그니처를 갖는 여러 메소드를 하나의 이름으로 정의하는 것이었다.
오버라이딩(overriding)이란 상속관계에 잇는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 시그니처를 갖는 메소드로 다시 정의하는 것이라고 할 수 있다.
자바에서 자식 클래스는 부모 클래스의 private 멤버를 제외한 모든 메소드를 상속받는다. 이렇게 상속받은 메소드는 그대로 사용해도 되고, 필요한 동작을 위해 재정의 하여 사용할 수도 있다.
즉, 메소드 오버라이딩이랑 상속받은 부모 클래스의 메소드를 재정의하여 사용하는 것을 의미한다.
자바에서는 메소드 오버라이딩을 통해 상속받은 부모 클래스의 메소드를 자식 클래스에서 직접 재정의할 수 있다.
class Parent {
void display() { System.out.println("부모 클래스의 display() 메소드입니다."); }
}
class Child extends Parent {
void display() { System.out.println("자식 클래스의 display() 메소드입니다."); }
}
public class Inheritance05 {
public static void main(String[] args) {
Parent pa = new Parent();
pa.display();
Child ch = new Child();
ch.display();
Parent pc = new Child();
pc.display(); // Child cp = new Parent();
}
}
구분 | 오버로딩(Overloading) | 오버라이딩(Overriding) |
---|---|---|
메소드 이름 | 동일 | 동일 |
매개변수,타입 | 다름 | 동일 |
리턴타입 | 상관없음 | 동일 |
정리하자면 오버로딩(overloading) 이란 서로 다른 시그니처를 갖는 동일한 이름의 여러 메소드를 여러개 정의하는 것이라면, 오버라이딩(overriding) 이란 상속관계에 있는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 시그니쳐를 갖는 메소드로 재정의하는 것이다.
활을 클래스로 보고 화살을 클래스의 메서드로 비유해보면, overloading은 멀티샷 같이 여러 종류의 화살촉을 여러개 걸어 발사하는 것이지만, overriding은 기존의 화살촉에 또다른 화살촉을 끼어넣어 발사하는 것으로 보면 된다.
class Parent {
void display() {
System.out.println("부모 클래스의 display() 메소드입니다.");
}
}
class Child extends Parent {
int count;
// 오버라이딩(overriding)된 display 메소드
void display() {
count++; // 자식의 인스턴스 변수를 증가시키고
System.out.println("자식 클래스의 display() 메소드입니다."); // 출력 내용도 다르게 한다.
}
// 오버로딩(overloading)된 display() 메소드
void display(String str) {
System.out.println(str); // 문자열을 입력값으로 받으면 그대로 출력
}
void display(int c) {
this.count += c;
System.out.println(count); // 정수를 입력값으로 받으면 자식 인스턴스 변수를 더해주고 더한 값을 출력
}
void display(boolean b) {
if(b == true) {
super.display(); // 만약 true를 입력값으로 받으면 부모 클래스의 메서드를 출력
}
}
}
public class Main {
public static void main(String[] args) {
Child ch = new Child();
// 오버라이딩(overriding) 된 자식 메서드 출력
ch.display(); // "자식 클래스의 display() 메소드입니다."
// 오버로딩(overloading) 된 자식 메서드 출력
ch.display("Hello World"); // "Hello World" 출력
ch.display(900); // 901
ch.display(true); // "부모 클래스의 display() 메소드입니다." - 오버라이딩 되었던 부모 메서드를 출력
}
}