Programming/Java

메소드 오버로딩(Overloading)과 오버라이딩(Overriding)의 차이 이해하기

Jan92 2022. 1. 3. 23:03

오버로딩(Overloading)과 오버라이딩(Overriding)

'다형성과 오버로딩, 오버라이딩'

 

다형성이란 하나의 메서드나 클래스가 있을 때 그것이 다양한 방법으로 동작하는 것을 말하며, 자바에서는 주로 오버로딩(Overloading)과 오버라이딩(Overriding)을 통해서 다형성을 지원합니다.

오버로딩과 오버라이딩은 이름이 비슷하여 헷갈릴 수 있지만 전혀 다른 개념이기 때문에 헷갈리지 않도록 확실한 차이점을 알아두는 것이 좋습니다.

 

'오버로딩(Overloading) / 확장'

같은 이름의 메서드 여러 개를 가지면서 매개변수의 유형과 개수가 다르도록 사용하는 것

 

'오버라이딩(Overriding) / 재정의'

상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의해서 사용하는 것

 

 


 

'오버로딩(Overloading)'

 

오버로딩은 메서드 오버로딩과 생성자 오버로딩이 있으며 실제 적용되는 것은 같습니다.

같은 이름의 함수(메서드)를 여러 개 정의하고, 매개변수의 유형과 개수를 다르게 하여 다양한 유형의 호출에 응답할 수 있도록 하는 방식입니다.

(일반적으로 하나의 클래스 안에 같은 이름의 메서드를 정의하게 되면 에러가 발생합니다.)

 

public class Test {

    // 매개변수가 없는 overloadingTest() method
    void overloadingTest(){
        System.out.println("매개변수를 받지 않는 메서드");
    }

    // 매개변수로 int형 인자 2개를 요청하는 overloadingTest(int a, int b) method
    void overloadingTest(int a, int b){
        System.out.println("int형 인자 2개를 요청하는 메서드 "+ a + ", " + b);
    }

    // 매개변수로 String형 인자 1개를 요청하는 overloadingTest(String str) method
    void overloadingTest(String str){
        System.out.println("String형 인자 1개를 요청하는 메서드 " + d);
    }
}

 

핵심이기 때문에 다시 한번 이야기하면 오버로딩은 '메서드의 이름은 같지만 매개변수의 개수나 타입이 반드시 달라야' 합니다.

 

오버로딩의 특징으로는 접근 제어자를 자유롭게 지정해줄 수 있다는 특징이 있습니다.

오버로딩된 각 메서드의 접근 제어자를 public, default, protected, private으로 다르게 지정해줘도 상관없습니다. 하지만 같은 매개변수 개수와 타입을 가지면서 접근 제어자만 다르게 한다고 오버로딩이 되지는 않습니다. 또한 매개변수는 같고 반환 타입이 다른 경우는 오버로딩이 성립되지 않습니다.

 

 

 

println()

'오버로딩을 사용하는 이유'

 

  • 첫 번째로는 같은 기능을 하는 메서드를 하나의 이름으로 사용할 수 있기 때문이며,
  • 두 번째로는 메서드의 이름을 절약할 수 있기 때문입니다.

 

많이 사용하는 println() 메서드를 예로 보면 이해가 쉽습니다. println() 메서드는 오버로딩 되어있기 때문에 int형 인자, String형 인자, boolean형 인자, char형 인자 모두 받아서 동작할 수 있습니다. 

 

만약 오버로딩이 없다면 int형 인자를 받는 메서드는 printlnInt()로 String형 인자를 받는 메서드는 printlnString()으로 boolean형 인자를 받는 메서드는 printlnBoolean()처럼 각각의 메서드 이름을 따로 만들어줘야 합니다.

오버로딩이 있기 때문에 같은 기능(콘솔로 인자를 출력하는)을 하는 메서드를 하나의 이름인 println()으로 사용할 수 있게 됩니다. 

 

 


 

'오버라이딩(Overriding)'

 

오버라이딩은 상위 클래스로부터 상속받은 메서드의 동작만을 재정의하는 것입니다.

상위 클래스가 가지고 있는 멤버 변수가 하위 클래스로 상속되는 것처럼 상위 클래스가 가지고 있는 메서드도 하위 클래스로 상속되어 하위 클래스에 사용할 수 있습니다. 

(상속받은 메서드를 그대로 사용할 수도 있지만, 필요에 따라 메서드를 재정의하여 사용하는 경우가 있습니다.)

 

오버라이딩은 쉽게 말해서 '메서드의 이름이 같고, 매개변수가 같고, 반환형이 같은 경우에 상속받은 메서드를 덮어쓴다'라고 생각할 수 있습니다.

 

public class Parent {
    public void overridingTest() {
        System.out.println("부모 메서드의 내용");
    }
}
public class Child extends Parent {
    @Override
    public void overridingTest() {
        System.out.println("부모 클래스의 메서드를 상속받아 내용을 재정의해서 사용");
    }
}

 

오버라이딩의 특징으로는 상위 클래스의 메서드보다 접근 제어자를 더 좁은 범위로 변경할 수 없다는 것과 상위 클래스의 메서드보다 더 큰 범위의 예외를 선언할 수 없다는 것입니다.

 

또한 상위 클래스의 static 메서드는 클래스에 속하는 메서드이기 때문에 상속되지 않고 따라서 오버라이드 되지도 않습니다.

(static 메서드는 런타임 시에 생성되는 것이 아니라 컴파일 시 생성되어 메모리에 적제 되는 방식이기 때문에 런타임 시에 해당 메서드를 구현한 실체 객체를 찾아가서 호출하게 됩니다. static 메서드에 대해서는 다형성이 적용되지 않습니다.)

 

final이 지정된 메서드 역시 오버라이드를 할 수 없으며, private 접근 제어자를 가진 메서드는 상속 자체가 불가능하기 때문에 오버라이드가 성립되지 않습니다.

(final의 경우 하위 클래스가 해당 메서드를 재정의 할 수 없도록 하기 위해서 사용됩니다.)

 

또 실무에서 인터페이스를 implements로 가져와서 인터페이스에 정의된 메서드를 @Override 어노테이션을 사용하여 오버라이딩해서 재정의하는데요. 이때 interface의 메서드를 오버라이드해서 구현하는 경우 반드시 public 접근 제어자를 사용해야 합니다.

 

 

***

@Override 어노테이션은 없어도 오버라이딩이 적용되어 정상적으로 동작합니다. 그렇다면 @Override 어노테이션을 쓰는 이유는 무엇일까요?

@Override 어노테이션은 시스템에서 오버라이딩한 메서드라고 알리는 역할로 오버라이딩이 잘못된 경우 경고를 줍니다.

 

예를 들어 백엔드 단에서 사용되는 라이브러리 중 하나가 업데이트되어 상속하는 클래스 메서드의 시그니처가 바뀌었습니다.

@Override 어노테이션이 적용되지 않은 상태에서는 전에 오버라이드 한 메서드가 업데이트 이후 그냥 추가적인 메서드로 인식되어 컴파일 오류가 발생하지 않습니다. 이때 @Override 어노테이션을 적용함으로써 의도적으로 컴파일 오류를 일으켜 작동방식이 바뀌는 것을 대비할 수 있습니다.

또한 @Override를 표시함으로써 코드 리딩 시에 해당 메서드가 오버라이딩하였다는 것을 쉽게 파악할 수 있다는 장점이 있습니다.