본문 바로가기

Java

[ 자바 / Java] 상속과 다형성, 그리고 오버라이딩(Overriding)

상속이란?

상속은 기존 클래스를 확장하여 새로운 클래스를 만드는 개념으로,

기존 클래스의 속성(필드)과 메서드를 상속받아 새로운 클래스를 만들 수 있습니다.

자손은 조상의 모든 멤버를 상속받습니다. (생성자, 초기화블럭 제외)

 

조상클래스

  • 상속의 대상이 되는 클래스로, 다른 클래스에서 공통적으로 사용되는 속성과 메서드를 가지고 있습니다. 상위 클래스는 다른 클래스에게 상속해주는 역할을 합니다. 상속을 받는 클래스에서는 상위 클래스를 조상 클래스(superclass) 또는 부모 클래스(parent class)라고 부릅니다.

 

자손클래스

  • 상위 클래스를 상속받아 새로운 클래스를 만드는 클래스로,  조상 클래스에서 정의된 모든 속성과 메서드를 상속받습니다. 그리고 자식 클래스는 상속 받은 속성과 메서드를 사용할 수 있을 뿐만 아니라, 자신만의 속성과 메서드를 추가 또는 자신에 맞게 변경할 수 있습니다.  자식 클래스(subclass) 또는 파생 클래스(derived class)라고 부릅니다.

상속 구현하기

자바에서 상속을 구현하기 위해서는 extends 키워드를 사용합니다.

extends 키워드를 사용하면 하위 클래스는 상위 클래스의 모든 멤버를 상속받게 됩니다.

하위 클래스에서는 상위 클래스의 멤버에 접근하기 위해 super 키워드를 사용할 수 있습니다.

 

 

class Parent {
  int x;
  
  void method() {
    System.out.println("Parent method");
  }
}

class Child extends Parent {
  int y;
  
  void method2() {
    System.out.println("Child method");
  }
}

위 코드에서 Child 클래스는 Parent 클래스를 상속받고 있습니다.

Child 클래스는 Parent 클래스의 x 속성과 method 메서드를 상속받으며,  Child 클래스는 자신만의 y 속성과 method2 메서드를 가지고 있습니다.

 

상속을 사용하는 상황?

비슷한 기능을 가진 여러 클래스를 개발할하여 여러 개의 클래스 내에 공통된 속성과 메서드가 존재할 때 사용합니다. 

공통된 부분을 모아 조상 클래스에서 정의하고,  이를 물려받는 자식 클래스를 만듭니다.

자식클래스는 조상 클래스의 속성과 메서드를 상속받고, 자식 클래스만의 속성과 메소드를 추가로 구현하면 됩니다.

 

상속의 장점

 

  1. 코드 재사용성이 높아진다.
    • 조상 클래스에서 정의된 속성과 메서드를 자식 클래스에서 다시 구현할 필요 없이 그대로 상속받아 사용할 수 있기 때문에, 비슷한 기능을 가진 여러 클래스를 개발할 때, 중복되는 코드를 줄일 수 있습니다.
  2. 유지보수가 용이해진다.
    • 코드 중복이 감소하면서 코드를 유지보수하기 쉬워집니다. 만약 공통적인 속성 또는 메서드를 수정해야 할 경우에는, 조상 클래스에서 수정하면 모든 자식 클래스에 적용됩니다.
  3. 다형성을 구현할 수 있다.
    • 상속을 통해 다형성(polymorphism)을 구현할 수 있습니다. 자식 클래스에서 조상 클래스의 메서드를 오버라이딩하면, 자식 클래스의 인스턴스에서는 오버라이딩된 메서드가 호출되기 때문에 다형성을 구현할 수 있습니다.
  4. 코드 가독성이 높아진다.
    • 코드가 간결해지고 가독성이 높아집니다. 상속을 통해 비슷한 기능을 하는 클래스들이 같은 조상 클래스를 상속하므로, 코드가 일관적이고 읽기 쉬워집니다.

반면, 상속을 사용하지 않으면, 각 클래스가 독립적으로 작성되므로, 코드의 중복이 많아질 수 있고, 유지보수하기 어려워집니다.

 

 


 

오버라이딩이란?

자바에서 오버라이딩(Overriding)은 상속 관계에 있는 두 클래스 사이에서 발생하는 개념으로,

부모 클래스가 가지고 있는 메서드를 자식 클래스에서 재정의하여 사용하는 것을 말합니다.

 

오버라이딩을 하기 위해서는 부모 클래스에서 정의한 메서드와 동일한 메서드 이름, 매개변수, 반환타입을 가진 메서드를 자식 클래스에서 선언하면 됩니다. (부모와 메서드 선언부가 일치해야함) 

자식 클래스에서 재정의한 메소드의 경우, 해당 메서드 호출 시 부모의 메서드가 아닌 자식 클래스에서 재정의한 메서드가 호출됩니다.

 

 

오버라이딩 규칙 

구분 규칙
메서드 시그니처 자식 클래스에서 오버라이딩한 메서드는
부모 클래스의 메서드와 시그니처 (이름, 매개변수, 반환 타입)가 동일해야 함 (선언부 변경불가, 구현부 변경가능)
접근 제어자 자식 클래스에서 오버라이딩한 메서드의 접근 제어자는
 부모 클래스의 메서드의 접근 제어자보다 같거나 더 넓어야 함
예외 처리 자식 클래스에서 오버라이딩한 메서드는
부모 클래스의 메서드에서 throws로 선언한 예외를 throws로 선언할 수 있지만, 더 많은 예외를 throws로 선언할 수는 없음
반환 타입 자식 클래스에서 오버라이딩한 메서드의 반환 타입
부모 클래스의 메서드의 반환 타입과 일치하거나, 부모 클래스의 메서드의 반환 타입의 하위 타입이어야 함

이러한 규칙을 지키지 않으면 컴파일 오류가 발생하거나, 런타임 오류가 발생할 수 있습니다.

 

public class Parent {
    public void printMessage() {
        System.out.println("Hello, World!");
    }
}

자식 클래스에서 오버라이딩한 printMessage 메서드는 부모 클래스의 printMessage 메서드와 이름, 매개변수, 반환타입이 동일합니다. 또한 @Override 어노테이션을 사용하여 이 메서드가 오버라이딩된 것임을 명시합니다.

이제 Child 클래스에서 printMessage 메서드를 호출하면 "Hello, Java!"가 출력되게 됩니다.

public class Child extends Parent {
    @Override
    public void printMessage() {
        System.out.println("Hello, Java!");
    }
}

 

오버라이딩과 객체의 동적바인딩

객체의 동적 바인딩(dynamic binding)은 프로그램이 실행될 때, 어떤 메서드를 호출할 것인지를 결정하는 과정을 말합니다.

자바에서는 메서드 호출 시, 해당 메서드를 찾기 위해 클래스의 메서드 테이블(virtual method table 또는 vtable)을 사용합니다. 이 테이블은 객체의 타입과 해당 타입에 정의된 메서드의 정보를 담고 있습니다.

동적 바인딩은, 프로그램 실행 중에 객체의 실제 타입에 따라 해당 객체에서 호출될 메서드가 결정되는 것입니다.

예를 들어, 아래 코드에서 animal.makeSound() 메서드가 호출될 때, animal 객체의 실제 타입에 따라 다르게 동작합니다.

public class Animal {
    public void makeSound() {
        System.out.println("소리");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("멍멍");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("냐옹");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        animal1.makeSound(); // "멍멍" 출력
        animal2.makeSound(); // "냐옹" 출력
    }
}

위의 코드에서,

Animal 클래스의 makeSound 메서드를 Dog와 Cat 클래스에서 오버라이딩하여 자식 클래스의 특정 동작에 맞게 재정의했습니다.

이를 통해 Animal 타입의 변수에 Dog와 Cat 인스턴스를 할당하고, 각각 makeSound 메서드를 호출하면, Animal타입의 animal1, animal2 변수가 실제로 참조하고 있는 자식클래스에서 makeSound메서드를 재정의하였는지 확인한 후

동적 바인딩을 통해 부모클래스가 아닌 자식 클래스에서 재정의한 makeSound 메서드가 호출되어 "멍멍"와 "냐옹"이 출력됩니다.

동적 바인딩은 런타임 시점에 결정되므로, 프로그램 실행 전까지는 어떤 메서드가 호출될 지 정확히 알 수 없습니다.

따라서, 자바에서는 메서드 호출 시 해당 메서드를 찾기 위해 클래스의 메서드 테이블을 사용하고, 동적 바인딩을 통해 다형성을 구현합니다.

 


다형성이란?

 

다형성은 여러가지 형태를 가질 수 있는 능력을 뜻하며,

자바에서 조상 타입의 참조변수로 자손타입 객체를 다루는 것을 의미합니다.

 

업캐스팅 과 다운캐스팅

 

1. 업캐스팅(Upcasting) : 조상 타입의 참조변수로 자손타입 객체를 다루는 것

(형변환연산자 생략가능)

자바에서는 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하고 있습니다. 

  • 부모 클래스로 자식 클래스의 인스턴스를 참조하는 것
  • 업캐스팅이 일어나면 메모리에서는 부모 클래스 타입으로 해당 객체를 저장하게 됩니다. 이때 객체의 원래 자식 클래스에 선언된 메서드와 멤버 변수는 모두 사용할 수 없게 됩니다. 대신 부모 클래스에 선언된 메서드와 멤버 변수만 사용 가능합니다.
  • 업캐스팅을 통해 여러 자식 클래스의 인스턴스를 부모 클래스의 인스턴스로 처리할 수 있습니다.

2. 다운캐스팅 (Downcasting): 자손 타입의 참조변수로 부모타입 객체를 다루는 것

(형변환연산자 불가)

 다운캐스팅은, 업캐스팅된 객체가 원래 자식 클래스의 인스턴스인 경우(해당 객체가 실제로 자식 클래스로 생성된 객체일 떄)만 가능합니다.

 

  • 자식 클래스로 부모 클래스의 인스턴스를 참조하는 것 
  • 다운캐스팅은 부모 클래스 타입으로 참조되고 있는 객체를 다시 자식 클래스 타입으로 참조하는 것을 말합니다. 
  • 업캐스팅으로 인해 부모 클래스 타입으로 참조되었던 객체가 원래 자식 클래스의 인스턴스였다면, 다운캐스팅을 통해  다시 자식 클래스 타입으로 변환할 수 있습니다.
  • 메모리 상에서는, 이미 생성된 객체가 새로운 참조변수를 가리키게 되는 것일 뿐 객체 자체에는 아무런 변화가 일어나지 않습니다.
  • 다운캐스팅을 하게 되면, 부모 클래스에서 상속받은 멤버변수, 메소드와 더불어 자식 클래스에서 추가로 선언된 멤버변수와 메서드도 다시 사용할 수 있게됩니다. 
class Animal {
    public void makeSound() {
        System.out.println("Animal is making a sound.");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog is barking.");
    }
    
    public void fetch() {
        System.out.println("Dog is fetching.");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Cat is meowing.");
    }
    
    public void scratch() {
        System.out.println("Cat is scratching.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 업캐스팅 Upcasting: "Dog"타입 객체를 Animal타입 참조변수로 다룰 수 있음
        Animal myAnimal = new Dog();
        myAnimal.makeSound(); // "Dog is barking." 출력
        
        //myAnimal.fetch(); 
        // Error: myAnimal은 Animal 타입으로, Animal 타입에는 fetch메소드가 없음


        // 다운캐스팅 Downcasting: 
        // 원래 "Dog"타입 객체인데 업캐스팅으로 부모타입의 클래스에 참조되었던 객체만 다시 다운캐스팅 가능
        
       
        if (myAnimal instanceof Dog) {
            Dog myDog = (Dog) myAnimal;
            myDog.fetch(); // "Dog is fetching." 출력
        }
        
       }
}

 

매개변수 다형성

 

자바에서 매개변수의 다형성은 다형성의 한 예로, 상위 클래스 타입의 매개변수에 하위 클래스 타입의 객체를 전달할 수 있도록 합니다. 이를 이용하여, 메서드가 다양한 객체들을 다룰 수 있게 됩니다.

즉, 참조형 매개변수는 메소드 호출시 자신과 같은 타입 또는 자손타입의 인스턴스를 받을 수 있습니다.

 

예를 들어, 동물 클래스, 그리고 이를 상속받는 Person, Cat 클래스가 있다고 가정해보겠습니다.

Person클래스와 Cat클래스는 makeSound()라는 메서드를  overriding하고 있습니다.

makeSound() 메서드를 호출할 때, 매개변수로 Animal타입의 자손타입인 Peson, Cat객체를 전달하는 것이 매개변수의 다형성입니다.

이렇게 하면, makeSound() 메서드는 Animal 타입 매개변수를 받아 다양한 객체를 다룰 수 있습니다.

아래는 예시 코드입니다.

 

public class Animal {
    public void makeSound() {
        System.out.println("인사");
    }
}

public class Person extends Animal {
    public void makeSound() {
        System.out.println("안녕하세요!");
    }
}

public class Cat extends Animal {
    public void makeSound() {
        System.out.println("나옹");
    }
}

public class AnimalTest {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Person person = new Person();
        Cat cat = new Cat();
        
        makeAnimalSound(animal); // "인사" 출력
        makeAnimalSound(person); // "안녕하세요!" 출력
        makeAnimalSound(cat); // "냐옹" 출력
    }
    
    public static void makeAnimalSound(Animal animal) {
        animal.makeSound();
    }
}

위 코드에서, makeAnimalSound() 메서드는 Animal 타입의 매개변수를 받습니다. 이 메서드를 호출할 때, Animal 객체나 Person, Cat객체를 전달할 수 있습니다. 이때, Person 객체를 전달하면 Person 클래스의 makeSound() 메서드가 호출되어 "안녕하세요!"가 출력되며, 마찬가지로 Cat객체를 전달하면 Cat클래스의 makeSound()매서드가 호출됩니다.

 

 

따라서, 자바에서 매개변수의 다형성을 이용하여, 메서드가 다양한 객체들을 다룰 수 있게 됩니다. 이를 이용하면, 코드의 재사용성과 유연성을 높일 수 있습니다.

 

Reference

http://www.tcpschool.com/java/java_polymorphism_concept#:~:text=%EB%8B%A4%ED%98%95%EC%84%B1(polymorphism)%EC%9D%B4%EB%9E%80%3F,%EC%9E%88%EB%8F%84%EB%A1%9D%20%ED%95%98%EC%97%AC%20%EA%B5%AC%ED%98%84%ED%95%98%EA%B3%A0%20%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4. 

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

https://docs.oracle.com/javase/tutorial/java/IandI/polymorphism.html

 

Polymorphism (The Java™ Tutorials > Learning the Java Language > Interfaces and Inheritance)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com