본문 바로가기

JAVA

Overloading과 OverRiding의 차이

요약

자바를 통해 프로그래밍을 해보신적이 있다면 Overloading과 OverRiding에 대해 접해 보셨을겁니다. 하지만 누군가가 Overloading이랑 OverRiding의 차이에 대해 물어본다면 무엇인지는 알지만 잘 정리된 말로 대답하기 쉽지 않습니다.

오버로딩과 오버라이딩은 메서드를 재정의하는 방법이지만, 목적과 사용 방법에 차이가 있습니다.
오버로딩은 같은 이름의 메서드를 여러 개 정의하되, 매개변수의 개수나 타입이 다르게 하는 것입니다.
이를 통해 같은 기능을 수행하되 다양한 매개변수를 받을 수 있는 메서드를 구현할 수 있습니다.
반면에 오버라이딩은 상속 관계에서 자식 클래스가 부모 클래스의 메서드를 재정의하는 것입니다. 
메서드의 시그니처는 동일하게 유지되지만, 구현 내용이 변경되어 자식 클래스의 요구에 맞게 동작할 수 있습니다. 
이를 통해 다형성을 구현할 수 있습니다.

이렇게 답변하실 수 있는 분은 이 글을 읽지 않으셔도 됩니다!


다형성

다형성이란 하나의 메서드나 클래스가 있을 때 그것이 다양한 방법으로 동작하는 것을 말하며, 자바에서는 주로 오버로딩과 오버라이딩을 통해서 다형성을 지원합니다.이 둘에 대한 개념을 처음 접하게 되면 헷갈릴 수 있는데 이 글을 읽는 여러분은 딱 한가지를 명심하셨으면 좋겠습니다

  • 오버로딩(확장의 개념) : 같은 이름의 메서드 여러개를 가지면서 매개변수의 유형과 개수가 다르도록 사용하는 것
  • 오버라이딩(재정의 개념) : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의해서 사용하는 것

오버로딩(Overloading)

오버로딩이란 같은 이름의 메서드를 여러개 정의하고, 매개변수의 유형과 개수를 다르게 하여 다양한 유형의 호출에 응답할 수 있도록 하는 방식입니다.
이러한 개념에 대해 처음 접해보시는 분은 이게 무슨말이야?? 라고 하실수 있으니 예제 코드와 함께 이해해보도록 하겠습니다!

public class OverLoadingTest {

    @Test
    void test(){
        System.out.println(3);
        System.out.println("3");
        System.out.println('3');
    }
}

우리가 익숙하게 사용하는 println() 메서드에는 이러한 오버로딩이 잘 반영되어있습니다.
위 코드의 경우 int타입인 3, String 타입인 "3", char 타입인 '3' 모두다 정상적으로 출력이 됩니다.
-> 예를들어 println(int x){ 내부 로직 }; 와 같이 println 메서드가 정의되어있다면 String 타입의 "3"을 넣었을때는 오류가 나야겠죠?

이를 이해하기 위해 println() 내부 코드를 살펴보겠습니다!

public void println(int x) {
        synchronized (this) {
            print(x);
            newLine();
        }
}

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
}

public void println(char x) {
        synchronized (this) {
            print(x);
            newLine();
        }
}

위와 같이 println()이라는 메서드는 오버로딩되어있기 때문에 위의 테스트 코드는 정상적으로 동작하는 것입니다 ^^
다시 한번 핵심을 짚어 드리자면 메서드 이름은 같지만 매개변수의 개수나 타입을 다르게 하여, 다양한 유형의 호출에 응답할 수 있도록 하는 것이 오버로딩입니다.


오버라이딩

오버라이딩은 상위 클래스로부터 상속받은 메서드의 동작만을 재정의하는 것입니다.
overriding을 한다면 상위 클래스가 가지고 있는 멤버 변수와 메서드를 하위 클래스에서 그냥 사용하거나, 재정의해서 사용할 수 있습니다.
이 또한 처음 접하면 모호하실수 있으니 코드로 살펴보겠습니다!

class Mother{
        int heritage = 1000;
        int getHeritage(){
           return this.heritage;
        }
}

class Son extends Mother{
}

@Test
void test(){
    Son son = new Son();
    System.out.println(son.getHeritage());
}

출력 : 1000

위의 Son 클래스는 Mother를 extends 하는 것 말고는 내부에 아무런 변수나 메서드가 정의되어있지 않습니다.
하지만 위의 테스트 코드는 정상적으로 동작합니다(son.getHeritage()) 왜그럴까요??
이는 바로 부모 클래스로부터 속성인 heritage와 메서드인 getHeritage()를 상속받았기 때문입니다!

public class OverLoadingTest {

    class Mother{
        int heritage = 1000;
        int getHeritage(){
            return this.heritage;
        }
    }

    class Son extends Mother{
        @Override
        int getHeritage() {
            return 1500;
        }
    }

    @Test
    void test(){
        Son son = new Son();
        System.out.println(son.getHeritage());
    }
}

출력 : 1500

위의 경우에는 왜 1500원이 출력될까요? 이는 바로 부모로 부터 물려받은 getHeritage()라는 메서드를 재정의하여 사용하였기 때문입니다
아직 어노테이션이 뭔지 모르신다면 @Override가 뭔지 모르실것 같아 간단히 설명드리면, 우선은 저러한 것을 적지 않아도 정상적으로 override 가능합니다.
하지만 @Override를 적을 시에는 오버라이딩이 잘못도 경우 경고를 줍니다

ex) 라이브러리 중 하나가 업데이트되어 상속하는 클래스 메서드의 시그니처가 바뀌었다, 이때 이 어노테이션을 적용하지 않았다면 오버라이드 한 메서드가 업데이트 이후 그냥 추가적인 메서드로 인식되어 컴파일 오류가 발생하지 않습니다.
하지만 이러한 어노테이션을 적용함으로서 컴파일 오류를 일으켜 작동방식이 바뀌는 것을 대비할 수 있고, 코드를 살펴보았을 때 다른 개발자라도 해당 메서드가 오버라이딩하였다는 것을 쉽게 파악할 수 있습니다.