자바(Java) - 상속

Jan 01, 2024
자바(Java) - 상속

상속

💡
상속(inheritance)은 기존에 존재하는 클래스로부터 필드와 메소드를 이어받고, 필요한 기능을 추가할 수 있는 기법이다.
 
상속은 검증된 소프트웨어를 재사용할 수 있어서 신뢰성 있는 소프트웨어를 손쉽게 개발, 유지 보수할 수 있게 해주는 중요한 기술이다.
 
상속을 이용하면 여러 클래스에 공통적인 코드들을 하나의 클래스로 모을 수 있어서 코드의 중복을 줄일 수 있다.
 
 

상속의 형식

💡
상속을 정의하려면 자식 클래스 이름 뒤에 extends를 쓰고 부모 클래스 이름을 적으면 된다.
 
class ElectricCar extends Car { } // ElectricCar : 자식 클래스 또는 서브 클래스 // Car : 부모 클래스 또는 슈퍼 클래스
 
class Car { int speed; // 속도 public void setSpeed(int speed) { // 속도 변경 메소드 this.speed = speed; } } class ElectricCar extends Car { int battery; // 추가된 필드 public void charge(int amount) { // 충전 메소드 battery += amount; } }
 
 

무엇이 상속되는가?

💡
자식 클래스는 부모 클래스가 가지고 있는 모든 멤버들을 전부 상속받고 자신이 필요한 멤버를 추가하기 때문에 항상 자식 클래스가 부모 클래스를 포함하게 된다.
 
아래는 위에서 쓴 부모 클래스와 자식 클래스를 main에서 쓰는 법이다.
public class ElectricCarTest { public static void main(String[] args) { ElectricCar obj = new ElectricCar(); // 자식 클래스 객체 생성 obj.speed = 10; obj.setSpeed(60);// 부모 클래스의 필드와 메소드 사용 obj.charge(10); // 자체 메소드 사용 } }
 
 

왜 상속을 사용하는가?

  1. 이미 존재하는 클래스의 필드와 메소드를 재사용할 수 있다.
 
  1. 중복되는 코드를 줄일 수 있다.
 
 

자바 상속의 특징

  1. 다중 상속을 지원하지 않는다. 다중 상속이란 여러 개의 클래스로부터 상속받는 것이다.
 
  1. 상속의 횟수에는 제한이 없다.
 
  1. 상속 계층 구조의 최상위에는 java.lang.Object 클래스가 있다.

상속과 접근 지정자

💡
앞에서는 부모 클래스의 모든 멤버가 상속되는 것으로 이야기 하였지만 사실은 상속시킬 멤버와 상속시키지 않을 멤버를 지정할 수 있다.
 
자식 클래스는 부모 클래스의 public 멤버, protected 멤버, 디폴트 멤버(부모 클래스와 자식 클래스가 같은 패키지 안에 있다면)를 상속받는다.
 
하지만 부모 클래스의 private 멤버는 상속되지 않는다.

상속과 생성자

💡
자식 클래스의 객체가 생성될 때, 부모 클래스의 생성자도 호출된다.
 
생성자의 호출 순서부모 클래스의 생성자 → 자식 클래스의 생성자 순으로 된다.
 
 

명시적인 호출

💡
자식 클래스의 생성자에서 명시적으로 부모 클래스의 생성자를 호출할 수 있다.
 
이 때 super라는 키워드가 사용된다.
 
class Base { public Base() { System.out.println("Base 생성자()"); } } class Derived extends Base { public Derived() { super(); // super()를 호출하면 부모 클래스의 생성자가 호출된다. System.out.println("Derived 생성자()"); } }
 
 

묵시적인 호출

💡
자바에서는 명시적으로 부모 클래스의 생성자를 호출해주지 않아도 자식 클래스의 객체가 생성될 때 자동적으로 부모 클래스의 기본 생성자가 호출된다.
 
class Base { public Base() { System.out.println("Base 생성자()"); } } class Derived extends Base { public Derived() { // 컴파일러는 부모 클래스의 기본 생성자가 자동으로 호출되도록 한다. System.out.println("Derived 생성자()"); } }
 
 

오류가 발생하는 경우

💡
묵시적인 부모 클래스 생성자 호출을 사용하려면 부모 클래스에 기본 생성자(매개 변수가 없는 생성자)가 반드시 정의되어 있어야 한다.
 
만약 기본 생성자가 정의되어 있지 않으면 오류가 발생한다.
 
class Base { public Base(int x) { System.out.println("Base 생성자()"); } } /* Base 클래스에는 이미 int 형의 인수를 가지는 생성자가 선언되어 있어서 컴파일러가 기본 생성자를 만들지 않았다. */ class Derived extends Base { public Derived() { /* 이럴 때는 super(100)과 같이 명시적으로 자식 클래스의 생성자 첫 부분에 부모 클래스의 생성자를 호출하는 문장을 넣으면 된다. */ // 아니면 Base 클래스에 기본 생성자를 만들어 주어도 된다. System.out.println("Derived 생성자()"); } } public class Test { publicd static void main(String[] args) { Derived obj = new Derived(); } }

메소드 오버라이딩

메소드 오버라이딩이란?

💡
메소드 오버라이딩(method overriding)은 자식 클래스가 부모 클래스의 메소드를 자신의 필요에 맞추어서 재정의하는 것이다.
 
이때 메소드의 이름이나 매개 변수, 반환형동일하여야 한다.
 
오버라이딩은 부모 클래스의 메소드를 무시하고 덮어씌운다는 의미이다.
 
자식 객체에서 해당 메소드를 실횅하면 오버라이딩된 메소드가 실행된다.
 
 

키워드 super를 사용하여 부모 클래스 멤버 접근

💡
부모 클래스의 메소드를 오버라이딩한 경우super를 사용하면 부모 클래스의 메소드를 호출할 수 있다.

다형성

💡
다형성(polymorphism)은 주로 프로그래밍 언어에서 하나의 식별자여러 개의 작업을 처리하는 것을 의미한다.
 
객체지향 프로그래밍에서 다형성이란 객체들이 똑같은 메시지를 받더라도 각자의 실제 타입에 따라서 서로 다른 동작을 하는 것을 말한다.
 
 

업캐스팅 vs 다운캐스팅

  • 업캐스팅(upcasting) :
    • 자식 객체를 부모 참조 변수로 참조하는 것이다.
    • 업캐스팅은 묵시적으로 수행될 수 있다.
    • 업캐스팅을 사용하면 부모 클래스의 멤버에 접근할 수 있다.
    • 하지만 자식 클래스의 멤버는 접근이 불가능하다.
 
  • 다운캐스팅(downcasting) :
    • 부모 객체를 자식 참조 변수로 참조하는 것이다.
    • 이것은 묵시적으로는 안 되고 명시적으로 하여야 한다.
 
class Parent { void print() { System.out.println("Parent 메소드 호출"); } } class Child extends Parent { @Override void print() { System.out.println("Child 메소드 호출"); } } public class Casting { public static void main(String[] args) { Parent p = new Child(); // 업캐스팅 : 자식 객체를 부모 객체로 형변환 p.print(); // 동적 메소드 호출, 자식의 print() 호출 // Child c = new Parent(); // 이것은 컴파일 오류이다. Child c = (Child)p; // 다운캐스팅 : 부모 객체를 자식 객체로 형변환 c.print(); // 메소드 오버라이딩, 자식 객체의 print() 호출 } }

상속 vs 구성

  • 상속 :
    • is - a” 관계이다.
    • 상속의 계층 구조를 올바르게 설계하였는지를 알려면 is-a관계가 성립하는지를 생각해보면 된다.
    • 상속 사용을 고려할 때는 항상 자식 클래스가 실제로 부모 클래스의 특수한 버전인지 자문해보자.
    •  
  • 구성 :
    • has - a” 관계이다.
    • “~은 ~을 가지고 있다” 와 같은 has - a(포함) 관계가 성립되면 이 관계는 상속으로 모델링을 하면 안 되고 구성을 사용하여야 한다.
    •  
class Vehicle {} class Engine {} class Brake {} public class Car extends Vehicle { private Engine e; private Brake b; public Car() { this.e = new Engine(); this.b = new Brake(); } }
Share article

stwin755