본문 바로가기
  • Let's study
알고리즘/Java

[Java] 상속 / 구성

by 코딩고수이고파 2026. 7. 14.

상속(Inheritance)은 기존 클래스의 기능을 물려받아 새로운 클래스를 만드는 객체지향 문법이다.

여러 클래스에 동일한 함수가 반복된다면, 공통 기능을 부모 클래스에 모아두고 자식 클래스가 이를 상속받아 사용할 수 있다.

상속을 사용하는 이유

동일한 코드가 여러 클래스에 반복되면 다음과 같은 문제가 발생한다.

  • 코드의 양이 많아진다.
  • 수정해야 할 부분이 여러 곳에 생긴다.
  • 유지보수가 어려워진다.

예를 들어 숨쉬다(), 야옹(), 뛰어넘다() 메서드가 모든 고양이 클래스에 반복된다면, 같은 코드를 여러 번 작성해야 한다.

이러한 중복 코드는 유지보수를 어렵게 만드는 대표적인 원인이다.

상속을 이용한 중복 제거

공통 기능을 부모 클래스인 고양이에 작성하고, 각 고양이 클래스는 extends를 사용하여 상속받는다.

class 고양이 {
    void 숨쉬다() {
        System.out.println("숨쉬다!!");
    }

    void 야옹() {
        System.out.println("야옹");
    }

    void 뛰어넘다() {
        System.out.println("뛰어넘다");
    }
}

class 검은고양이 extends 고양이 {
    void 미래를_예지하다() {
        System.out.println("미래를_예지하다");
    }
}

class 흰고양이 extends 고양이 {
    void 목숨을_늘린다() {
        System.out.println("목숨을_늘린다");
    }
}

class 붉은고양이 extends 고양이 {
    void 빠르게_이동() {
        System.out.println("빠르게_이동");
    }
}
 

extends를 사용하면 자식 클래스는 부모 클래스의 메서드를 그대로 사용할 수 있다.

흰고양이 cat = new 흰고양이();

cat.숨쉬다();
cat.야옹();
cat.뛰어넘다();
cat.목숨을_늘린다();

 

공통 기능은 부모 클래스에 작성하고, 자식 클래스는 자신만의 기능만 추가하면 된다.

숨쉬다(), 야옹(), 뛰어넘다()는 부모 클래스에서 상속받았으며, 목숨을_늘린다()는 흰고양이만의 기능이다.

상속의 장점

  • 중복 코드를 제거할 수 있다.
  • 공통 기능을 한 곳에서 관리할 수 있다.
  • 부모 클래스만 수정하면 모든 자식 클래스에 변경 사항이 반영된다.
  • 코드의 재사용성이 높아진다.
  • 유지보수가 쉬워진다.

상속의 계층 구조

상속은 부모 클래스 하나만 상속받는 것이 아니라 여러 단계(계층)로 이어서 사용할 수 있다.

예를 들어 다음과 같은 계층 구조를 만들 수 있다.

동물
  ↑
고양이
  ↑
검은고양이
 

검은고양이는 고양이를 상속받고, 고양이는 다시 동물을 상속받는다.

이처럼 자식 클래스는 부모뿐만 아니라 조상 클래스의 기능까지 모두 상속받는다.

메서드 오버라이딩(Overriding)

상속받은 메서드를 자식 클래스에서 다시 정의하는 것을 오버라이딩(Overriding)이라고 한다.

부모 클래스의 기능을 그대로 사용할 수도 있고, 자식 클래스에 맞게 변경할 수도 있다.

class 동물 {
    void 울다() {
        System.out.println("울다");
    }
}

class 고양이 extends 동물 {
    void 울다() {
        System.out.println("야옹");
    }
}
 

위 코드에서 고양이는 부모의 울다() 메서드를 자신에게 맞게 다시 정의하였다.

상속만으로는 중복을 모두 해결할 수 없다.

상속은 공통 기능을 부모 클래스로 분리하여 중복을 줄일 수 있지만, 모든 중복을 해결할 수는 없다.

예를 들어 서로 다른 클래스들이 같은 기능을 필요로 하지만 상속 관계가 아니라면, 상속만으로는 중복을 제거할 수 없다.

또한 하나의 클래스만 상속할 수 있는 단일 상속의 제약도 있기 때문에, 상속만으로 모든 구조를 설계하기는 어렵다.

이러한 경우에는 인터페이스(Interface), 구성(Composition) 등의 객체지향 기법을 함께 사용하여 중복을 해결한다.

구성(Composition)

구성(Composition)은 객체가 다른 객체를 가지고 있으면서 필요한 기능을 협력하여 수행하는 객체지향 설계 방식이다.

즉, 어떤 객체가 직접 기능을 구현하지 못하더라도 다른 객체에게 일을 맡겨 기능을 수행할 수 있다.

예를 들어 홍길순은 자바 개발을 할 수 없지만, 홍길동 객체를 멤버 변수로 가져와 홍길동에게 자바 개발을 요청하여 기능을 수행할 수 있다.

class 홍길동 {
    void 자바개발() {
        System.out.println("자바 25 개발을 합니다.");
    }

    void C언어개발() {
        System.out.println("C언어 개발을 합니다.");
    }
}

class 홍길순 {
    홍길동 홍길동연락처 = new 홍길동();

    void 자바개발() {
        홍길동연락처.자바개발();
    }

    void 파이썬개발() {
        System.out.println("파이썬 개발을 합니다.");
    }
}
 

위 코드에서 홍길순은 홍길동 객체를 가지고 있으며, 자바개발()이 호출되면 직접 구현하는 대신 홍길동 객체의 자바개발()을 호출한다.

이처럼 객체 간의 협력을 통해 기능을 수행하는 방식을 구성(Composition)이라고 한다.

상속보다 구성을 선호하는 이유

상속은 공통 기능을 재사용하는 좋은 방법이지만, 부모 클래스와 자식 클래스가 강하게 연결되는 단점이 있다.

반면 구성은 필요한 객체를 멤버 변수로 사용하여 기능을 위임하기 때문에 더 유연한 설계가 가능하다.

구성의 장점

  • 객체 간 협력을 통해 기능을 수행할 수 있다.
  • 클래스 간의 결합도가 낮아져 변경에 유연하다.
  • 캡슐화를 유지하기 쉽다.
  • 실행 중에도 다른 객체로 교체하여 동작을 변경할 수 있다.
  • 다중 상속에서 발생할 수 있는 문제를 피할 수 있다.

객체의 형변환(Upcasting)

객체의 형변환은 상속 관계에서만 가능하다.

자식 객체를 부모 타입으로 변환하는 것을 업캐스팅(Upcasting)이라고 하며, 이는 자동으로 이루어지고 안전한 형변환이다.

무기 weapon = new 칼();
 

위 코드에서 실제 객체는 칼이지만, 참조 변수의 타입은 부모 클래스인 무기이다.

이처럼 하위 타입(자식) → 상위 타입(부모) 으로의 형변환은 자동으로 이루어진다.

부모 타입으로 자식 객체 참조하기

하나의 부모 타입 변수로 여러 종류의 자식 객체를 참조할 수 있다.

무기 weapon = new 칼();
weapon = new 활();
 

weapon의 타입은 항상 무기이지만, 실제 가리키는 객체는 칼이 될 수도 있고 활이 될 수도 있다.

오버라이딩

부모 클래스의 메서드를 자식 클래스에서 오버라이딩하면, 부모 타입으로 참조하더라도 실제 객체의 메서드가 실행된다.

public class Main {
    public static void main(String[] args) {

        무기 weapon = new 칼();
        weapon.공격(); // 칼로 공격합니다.

        weapon = new 활();
        weapon.공격(); // 활로 공격합니다.
    }
}

class 무기 {
    void 공격() {
        System.out.println("무기로 공격합니다.");
    }
}

class 칼 extends 무기 {
    void 공격() {
        System.out.println("칼로 공격합니다.");
    }
}

class 활 extends 무기 {
    void 공격() {
        System.out.println("활로 공격합니다.");
    }
}

부모 타입 변수는 여러 자식 객체를 가리킬 수 있으므로 공통 인터페이스(리모컨) 역할을 한다.

무기 weapon = new 칼();
weapon.공격();

weapon = new 활();
weapon.공격();
 

같은 공격() 메서드를 호출했지만, 실제 객체가 무엇인지에 따라 실행되는 메서드가 달라진다.

즉, 부모 타입 하나로 다양한 자식 객체를 제어할 수 있다.

'알고리즘 > Java' 카테고리의 다른 글

[Java] static 변수 / 메서드  (0) 2026.07.14
[Java] 리스트(List) / 맵(Map)  (0) 2026.07.14
[Java] 함수  (0) 2026.07.13
[Java] 클래스  (0) 2026.07.13
[Java] 배열의 복사  (0) 2026.07.13

댓글