1. 상속이란 무엇인가?
상속은 기존 클래스를 기반으로 새 클래스를 만들면서 그 속성과 메서드를 “상속”받는 메커니즘입니다. 새 클래스는 하위 클래스(또는 자식 클래스)라 하고, 기존 클래스는 상위 클래스(또는 기본 클래스)라 합니다.
마치 예전 레시피를 바탕으로 새로운 케이크를 만드는 것과 같습니다. 좋은 부분은 모두 가져오고, 여기에 몇 가지 새로운 재료를 더하면 — “할머니 레시피에 비밀 재료를 더한 케이크”가 완성되는 셈이죠.
- 상위 클래스 — 공통 속성과 동작을 담습니다.
- 하위 클래스 — 이를 모두 물려받고, 자신만의 것을 추가하거나 동작을 변경할 수 있습니다.
예를 들어, “고양이는 동물이다”: 모든 고양이는 동물이지만 모든 동물이 고양이는 아닙니다. 동물은 기본 클래스가 될 수 있고, 고양이는 그 하위 클래스가 됩니다. 개도 마찬가지죠.
또 “원은 도형이다”: 모든 원은 도형이지만 모든 도형이 원은 아닙니다. 도형은 상위 클래스가 될 수 있고, 원과 삼각형이 그 하위 클래스가 됩니다. Java에서는 이런 관계를 키워드 extends로 구현합니다.
왜 상속이 필요한가?
백만 달러짜리 질문: 왜 한 클래스의 코드를 다른 클래스에 그냥 복사하지 않을까요? 상속은 왜 존재할까요?
- 코드 재사용: 공통된 것은 기본 클래스로 뽑아냅니다. 필드와 메서드를 중복해서 작성할 필요가 없고 — 하위 클래스에서 자동으로 사용할 수 있습니다. 예를 들어, 대학교 프로그램에서 사람은 기본 클래스가 될 수 있고, 최소한 이름과 생일을 가질 것입니다. “학생”과 “교수”는 각자의 속성을 더한 하위 클래스가 될 수 있죠.
- 확장과 특화: 필요한 곳에서만 새로운 속성/메서드를 추가하거나 기존 동작을 바꿀 수 있습니다. 모든 동물은 소리를 내지만, 고양이는 야옹, 개는 멍멍이죠. 또한 고양이에만 “나무 타기” 능력을 추가할 수도 있습니다(기본 클래스에는 없는 능력).
- 계층 구조화: 코드를 더 논리적이고 이해하기 쉬운 구조로 만듭니다. 예를 들어 Animal, Dog, Cat 클래스가 있다면, Dog와 Cat이 Animal의 특수한 경우라는 점이 바로 드러납니다.
예시. 만약 Java에 상속이 없다면, eat(), sleep() 같은 메서드와 name 필드를 동물마다 매번 복사해야 할 것입니다. 내일 breathe() 메서드를 추가해야 한다면 수십 개 클래스를 모두 수정해야 하겠죠! 상속을 사용하면 — 기본 클래스에 “숨쉬기”만 추가하면 됩니다.
2. 상속 문법: extends
Java에서 상속을 어떻게 선언할까요? 아주 간단합니다. 키워드 extends를 사용하면 됩니다.
class RoditelskiyKlass {
// 필드와 메서드
}
class Podklass extends RoditelskiyKlass {
// 추가 필드와 메서드
}
예제 1: 동물
// 부모 클래스
class Animal {
String name;
void eat() {
System.out.println(name + " 먹는다.");
}
}
// 하위 클래스
class Cat extends Animal {
void meow() {
System.out.println(name + " 말한다: 야옹!");
}
}
여기서 무슨 일이 일어나나요?
- Cat 클래스는 Animal 클래스의 모든 필드와 메서드를 상속합니다.
- 즉, Cat에는 우리가 명시적으로 적지 않았어도 이미 name 필드와 eat() 메서드가 있습니다.
- 게다가 Cat만의 메서드 meow()가 추가됩니다.
사용 예
public class Main {
public static void main(String[] args) {
Cat kitty = new Cat();
kitty.name = "바르식";
kitty.eat(); // 바르식이 먹는다. (상속됨)
kitty.meow(); // 바르식이 말한다: 야옹! (고유 메서드)
}
}
완성! 최소한의 코드로 중복 없이 새로운 클래스를 얻었습니다.
3. 하위 클래스는 무엇을 상속하나요?
Java에서 하위 클래스가 상속하는 것은 다음과 같습니다.
- 기본 클래스의 모든 public(public) 및 protected(protected) 필드와 메서드.
- 하위 클래스가 같은 패키지에 있다면 모든 package-private 필드와 메서드.
상속하지 않는 것:
- 생성자(하위 클래스에서 명시적으로 선언해야 합니다).
- private(private) 필드와 메서드(기본 클래스 내부에서만 접근 가능합니다).
- 정적 초기화 블록(상속되지는 않지만 각 클래스에 자체적으로 있을 수 있습니다).
- 하위 클래스가 다른 패키지에 있을 경우 package-private 접근을 가진 멤버.
그림으로 보기
class Animal {
public String name;
private int age;
public void eat() {
System.out.println("먹는다");
}
private void secret() {
System.out.println("비밀!");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("멍!");
}
}
- Dog은 name 필드와 eat() 메서드를 상속합니다.
- Dog은 age 필드와 secret() 메서드를 볼 수 없습니다 — 둘 다 private입니다.
4. Java에서의 상속 제한
Java는 꽤 엄격하며, 다음과 같은 규칙이 있습니다:
클래스의 단일 상속만 허용: 각 클래스는 오직 하나의 다른 클래스만 상속할 수 있습니다. 대신 하나의 클래스를 상속하는 하위 클래스는 얼마든지 있을 수 있습니다.
class Dog extends Animal { ... } // OK
class Dog extends Animal, Pet { ... } // 오류! 이렇게는 안 됩니다
인터페이스의 다중 상속 — 가능합니다. 하지만 이것은 별도의 주제라 나중에 다루겠습니다.
Object 클래스 — Java의 모든 클래스의 조상입니다. extends를 명시적으로 쓰지 않아도 여러분의 클래스는 암묵적으로 Object를 상속합니다.
5. 실용 예제
우리의 “동물원” 애플리케이션을 계속 만들어 봅시다.
1단계. 기본 클래스 Animal
class Animal {
String name;
void eat() {
System.out.println(name + " 먹는다.");
}
}
2단계. 하위 클래스 Cat
class Cat extends Animal {
void meow() {
System.out.println(name + " 말한다: 야옹!");
}
}
3단계. 하위 클래스 Dog
class Dog extends Animal {
void bark() {
System.out.println(name + " 말한다: 멍!");
}
}
4단계. 클래스 사용하기
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "무르카";
cat.eat(); // 무르카가 먹는다.
cat.meow(); // 무르카가 말한다: 야옹!
Dog dog = new Dog();
dog.name = "샤릭";
dog.eat(); // 샤릭이 먹는다.
dog.bark(); // 샤릭이 말한다: 멍!
}
}
무엇을 얻었나요?
- Cat과 Dog 두 클래스 모두 기본 클래스에서 name 필드와 eat() 메서드를 공유해 사용합니다.
- 각 클래스는 자신만의 고유 동작을 추가합니다: 고양이는 meow(), 개는 bark().
- 동물들이 “먹는 방법”을 바꾸고 싶다면, Animal 클래스의 eat() 메서드만 수정하면 — 모든 하위 클래스에 자동으로 반영됩니다.
6. 유용한 팁
시각화: 클래스 계층
계층을 다음처럼 그려볼 수 있습니다:
. Animal
/ \
Cat Dog
- Animal — 기본 클래스이며, 동물에 공통적인 것을 담습니다.
- Cat과 Dog — 하위 클래스로, 각자의 고유한 특징을 추가합니다.
생성자와 상속, 조금 더
생성자는 상속되지 않습니다. 하위 클래스에서 생성자를 정의하지 않으면, Java는 자동으로 기본 생성자(상위 클래스에 있을 경우)를 추가합니다. 상위 클래스에 매개변수가 있는 생성자만 있다면, 하위 클래스에서 반드시 super(...)로 명시적으로 호출해야 합니다. 간단한 예:
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
Cat(String name) {
super(name); // 부모 생성자 호출
}
}
7. 상속 사용 시 흔한 실수
오류 №1: 한 번에 여러 클래스를 상속하려는 시도.
Java에서는 class Cat extends Animal, Pet처럼 쓸 수 없습니다. 부모는 하나뿐!
오류 №2: private 필드와 메서드가 하위 클래스에서 접근 가능하다고 기대함.
private 멤버는 상속되지 않습니다(정확히는 존재하지만 하위 클래스에서 접근할 수 없음). 상속받는 쪽에서 접근을 허용해야 한다면 protected를 사용하세요.
오류 №3: 부모 생성자 호출을 잊음.
기본 클래스에 매개변수가 없는 생성자가 없는데 super(...)를 명시적으로 호출하지 않으면 컴파일 오류가 발생합니다.
오류 №4: “코드 절약”만을 위한 상속 사용.
클래스 간에 “is-a” 관계가 없다면 상속을 사용하지 않는 것이 좋습니다. 예를 들어, “사람”은 “자동차”를 상속받아서는 안 됩니다. 비록 둘 다 speed 필드를 가진다 해도요.
GO TO FULL VERSION