상속(Inheritance)
- 클래스 간에 코드를 재사용하고 계층적 구조를 만들기 위한 중요한 객체 지향 프로그래밍의 특징 중 하나
- 기존 클래스에 기능 추가 및 재정의하여 새로운 클래스를 정의
- 새로운 클래스 뒤에 extends와 확장할 클래스를 적어서 표현
ex) class laptop extends computer
부모 클래스: 상속 대상이 되는 기존 클래스 = 상위 클래스, 기초 클래스
자식 클래스: 기존 클래스를 상속하는 클래스 = 하위 클래스, 파생 클래스
- 부모 클래스의 필드와 메소드가 상속됨(생성자, 초기화 블록은 상속되지 않음)
- 같은 내용의 코드를 한 곳에서 관리함으로써 코드의 중복이 줄어듦
- 다중 상속은 불가능
- private, default 멤버는 자식 클래스에서 접근 불가(default의 경우, 내부 패키지의 자식 클래스는 가능)
public class Animal { //부모 클래스
private String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
public void sleep() {
System.out.println(name + " is sleeping.");
}
}
public class Dog extends Animal { //상속받는 클래스(자식 클래스)
public Dog(String name) {
super(name);
}
// 메소드 오버라이드
@Override
public void eat() {
super.eat();
System.out.println("The dog prefers meat!");
}
// Dog 클래스만의 메소드
public void bark() {
System.out.println("Woof! Woof!");
}
}
클래스 간 관계
상속 관계
- is a 관계
- 부모 클래스, 자식 클래스
포함 관계
- has a 관계
- 한 클래스를 다른 클래스의 참조변수로 사용하는 경우
super, super()
super
- 부모 클래스와 자식 클래스의 멤버 이름이 같을 때 구분하는 키워드
- super 키워드는 부모 클래스의 필드나 메소드를 참조할 때 사용
super()
- 부모 클래스의 생성자 호출
- 자식 클래스의 생성자 내에서 사용되며, 주로 부모 클래스의 초기화 로직을 실행하고 싶을 때 사용
- Object 클래스를 제외하고 모든 클래스에는 this() 혹은 super()를 호출해야 함
- 만약 자식 클래스의 생성자에서 super()를 명시적으로 호출하지 않는다면, 컴파일러는 부모 클래스의 기본 생성자를 호출하는 super()를 자동으로 추가
- 상속받는 클래스에서 생성자가 있으면 명시적으로 super()을 호출해야 함
class Parent {
String message;
Parent() {
message = "This is the parent class.";
}
Parent(String customMessage) {
message = customMessage;
}
void display() {
System.out.println(message);
}
}
class Child extends Parent {
Child() {
// 부모 클래스의 기본 생성자를 호출
super();
}
Child(String customMessage) {
// 부모 클래스의 매개변수를 받는 생성자를 호출
super(customMessage);
}
@Override
void display() {
super.display(); // 부모 클래스의 display() 메소드를 호출
System.out.println("This is the child class.");
}
}
public class Test {
public static void main(String[] args) {
Child child1 = new Child();
child1.display();
System.out.println("------------");
Child child2 = new Child("Custom message from parent.");
child2.display();
}
}
This is the parent class.
This is the child class.
------------
Custom message from parent.
This is the child class.
오버라이딩(Overriding)
- 부모 클래스의 메소드를 자식 클래스에서 재정의
- 부모 클래스로부터 상속받은 메서드의 내용을 변경하는 것
- @Override 어노테이션을 사용하여 컴파일러에게 오버라이딩임을 알려주면, 컴파일러가 오버라이딩의 규칙을 체크
- 오버라이딩 조건
1. 메소드의 이름, 매개변수, 반환 타입이 슈퍼 클래스의 메소드와 동일해야 함
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
// 메소드 오버라이딩
@Override
void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
// 메소드 오버라이딩
@Override
void sound() {
System.out.println("Cat meows");
}
}
public class TestOverride {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.sound(); // 출력: Dog barks
myCat.sound(); // 출력: Cat meows
}
}
2. 반환 타입에 한해, 부모 클래스의 반환 타입으로 변환할 수 있는 타입으로 변경 가능
- Java 5부터는 메소드 오버라이딩에서 "공변 반환 타입"을 지원한다. 이는 오버라이드된 메소드가 슈퍼 클래스의 메소드의 반환 타입의 하위 클래스 타입을 반환할 수 있게 한다.
class Animal {
Animal get() {
return this;
}
}
class Dog extends Animal {
@Override
Dog get() { // 반환 타입이 Animal의 하위 클래스인 Dog
return this;
}
}
3. 부모 클래스의 메소드보다 접근제어자를 더 좁은 범위로 변경 불가
class Animal {
protected void display() {
System.out.println("This is an animal");
}
}
class Dog extends Animal {
@Override
public void display() { // 접근 제어자가 protected에서 public으로 넓어짐
System.out.println("This is a dog");
}
// 오류 발생 예:
// private void display() {} // 부모 클래스보다 접근 제어자가 좁아짐. 컴파일 오류 발생
}
4. 부모 클래스의 메소드보다 더 큰 범위의 예외 선언 불가
- 더 적거나, 동일하거나 부모 클래스의 메소드에서 선언된 예외의 하위 예외를 선언할 수 있다.
class Animal {
void eat() throws Exception {
// ...
}
}
class Dog extends Animal {
@Override
void eat() { // 예외 선언이 없음. 문제 없음
// ...
}
// 오류 발생 예:
// void eat() throws Exception, IOException {} // 부모 클래스보다 예외가 더 많음. 컴파일 오류 발생
}
5. 인스턴스 메서드를 static 제어자로 바꿀 수 없음
오버라이딩 vs 오버로딩
- 오버라이딩(overriding)
부모 클래스에 존재하는 메서드를 자식 클래스에서 재정의하는 것(modify)
- 오버로딩(overloading)
같은 함수의 이름으로 매개변수를 달리하여 새롭게 만드는 것(new)