본문 바로가기

JAVA

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

  오버로딩(Overloading) 오버라이딩(Overriding)
목적 한 클래스 내에서 같은 이름의 메서드를 여러 개 정의하여, 매개변수의 타입이나 개수에 따라 다른 동작을 수행하도록 함 상속 관계에서 자식 클래스가 부모 클래스의 메서드를 재정의하여, 같은 메서드 호출에 대해 다른 동작을 수행하도록 함
바인딩 정적 바인딩 동적 바인딩
다형성 O O
매개 변수 메서드 이름은 같지만, 매개 변수의 타입, 개수 또는 순서가 달라야 함 메서드 이름, 매개 변수의 타입 및 개수가 부모 클래스에 있는 메서드와 정확히 동일해야 함
반환 타입 영향을 주지않음. 다양한 반환 타입이 가능함 반환 타입이 부모 클래스의 메서드와 호환되어야 함 즉, 같거나 자식 타입일 수 있음
접근 제한자 영향을 주지 않음 자식 클래스에서 오버라이딩하는 메서드는 부모 클래스의 메서드보다 같거나 더 넓은 접근 범위를 가져야 함
사용 상황 메서드를 호출할 때 매개 변수에 의해 어떤 메서드가 호출될지 결정됨 객체의 실제 타입에 의해 어떤 메서드가 호출될지 결정됨
키워드 - @Override 어노테이션을 사용하여 오버라이딩임을 명시적으로 표시할 수 있음

 

 

위의 사실에서 더 자세히 짚고 가자면, 

일반적으로 오버로딩은 한 클래스 내에서 일어난다고 하고 또 많이 사용하지만, 상속 관계에서도 일어날 수 있다. 상속받은 클래스에서 부모 클래스에 있는 메서드와 동일한 이름의 메서드를 다른 매개 변수로 정의할 수 있으며, 이는 오버로딩으로 간주된다. 오버로딩의 확장된 정의라고 생각하면 쉬울 것 같다.

class Parent {
    public void display() {
        System.out.println("Parent display()");
    }
}

class Child extends Parent {
    public void display(int number) { // 오버로딩된 메서드
        System.out.println("Child display(int number)");
    }
}

 

 

그렇다면 아래와 같이 아예 독립적인 관계에서는 조건이 같다면 오버로딩이라고 할 수 없는 걸까?

class ClassA {
    public void display() {
        System.out.println("Display in ClassA");
    }
}

class ClassB {
    public void display() {
        System.out.println("Display in ClassB");
    }
}

 

아예 관계가 없는 서로 독립적인 클래스 사이에서는, 각각의 클래스가 동일한 메서드 이름을 가진 메서드를 정의하고 있더라도, 그것을 '오버로딩'이라고 일반적으로 언급하지는 않는다. 오버로딩이라는 용어는 주로 단일 클래스 내부에서, 또는 상속 관계에 있는 클래스들에서 같은 이름의 메서드를 다른 매개변수 목록을 가지고 여러 번 정의하는 경우에 사용된다. 위와 같은 경우는 단순히 각 클래스가 자신의 동작을 구현하기 위해 해당 메서드 이름을 선택했을 뿐이다.

 

따라서, 오버로딩은 특정 클래스 내부 또는 클래스 계층 내에서 메서드의 다양성을 제공하는 메커니즘으로 이해되어야 하며, 서로 관계가 없는 클래스 사이에서는 적용되지 않는다.

 

 

다음은 바인딩에 대한 설명이다.

정적 바인딩

정적 바인딩은 컴파일 시점에 실행될 메서드가 결정되며, 오버로딩을 통해 구현된다. 컴파일러는 메서드를 호출할 때 매개변수의 타입과 개수를 기준으로 호출할 메서드를 결정한다. 이는 메서드 호출 시점에 어떤 메서드 버전이 호출될지 컴파일러가 결정한다는 의미에서 정적(static)이라고 한다.

 

동적 바인딩

동적 바인딩은 프로그램이 실행될 때, 즉 런타임 시점에 실행될 메서드가 결정되며, 오버라이딩과 상속을 통해 구현된다. 오버라이딩된 메서드는 실행 시점에 객체의 실제 타입을 기반으로 호출되는 메서드가 결정된다. 메서드 호출이 실행 시간(Runtime)에 결정된다는 의미에서 동적(dynamic)이라고 한다.

 

동적 바인딩의 한 예시로 객체의 참조 변수가 여러 형태의 객체를 참조할 수 있도록 함으로써 구현될 수 있다. 예를 들어, 부모 클래스 타입의 참조 변수가 자식 클래스 타입의 인스턴스를 참조할 수 있다. 아래의 코드를 보자. 

class Animal {
    public void makeSound() {
        System.out.println("Some generic sound");
    }
}

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

public class TestPolymorphism {
    public static void main(String[] args) {
        Animal myAnimal = new Dog();
        myAnimal.makeSound(); // 출력: "Bark"
    }
}

 

프로그램에서 Animal 타입의 참조 변수가 실제로는 Dog 객체를 참조하고 있을 때, makeSound 메서드를 호출하면, 자바 런타임은 참조 변수의 실제 객체 타입인 Dog를 확인하고 Dog 클래스에 오버라이딩된 makeSound 메서드를 실행한다.

 

이 예에서 myAnimal.makeSound(); 호출은 컴파일 시점에는 Animal 클래스의 makeSound 메서드를 호출하는 것처럼 보인다. 그러나 실행 시점에는 참조 변수 myAnimal이 실제로 참조하는 객체의 타입이 Dog임을 확인하고, Dog 클래스에서 오버라이딩한 makeSound 메서드를 호출한다. 이처럼 실행 시점에 메서드 호출이 결정되므로, 이를 동적 다형성, 즉 동적 바인딩이라고 한다.

 

이 때, 부모 클래스 타입의 참조 변수로 자식 클래스의 인스턴스를 참조한 위와 같은 경우를 업캐스팅(Upcasting)이라고 한다. 즉, 하위클래스로 만든 객체를 상위 클래스의 타입(자료형)으로 담는 것이다. 한편 다운캐스팅(Downcasting)은 부모 클래스 타입에 담긴 자식 객체를 다시 자식 클래스 타입으로 바꾸는 것이다.

 

 

다음으로 알아볼 것은 다형성이다.

객체지향 프로그래밍 언어의 핵심 개념 중 하나로 여러 가지 형태를 가질수 있는 능력을 의미한다. JAVA에서는 주로 다형성에 대해 정의할 때는 한 타입의 참조 변수를 통해 여러 타입의 객체를 참조할 수 있도록 하는 것이라고 정의한다. 즉, 상속 관계에서 오버라이딩(재정의)가 되었을 때 업캐스팅을 하여 다형성이라는 성질을 코드에 적용하고, 프로그램 실행 시 동적바인딩으로 재정의된 메서드가 실행됐을 때 다형성을 실현하는 것이다.

 

지금부터는 필자의 생각인데 업캐스팅이 없는 오버라이딩도 같은 이름의 메서드를 다르게 재정의해 사용한다는 점에서 이 자체만으로도 다형성을 보여준다고 생각한다.

또한 오버로딩처럼 같은 이름의 메서드가 여러가지 자료형을 인수로 받아서 동작하는 것도 다형성이라고 생각한다.

꼭 한 타입의 참조 변수가 여러 타입의 객체를 참조하지 않더라도 다형성 자체의 의미인 다양한 형태를 생각해보면 충분히 오버라이딩과 오버로딩 자체만으로 다형성을 보여준다고 생각한다.

 

결론적으로 오버로딩과 오버라이딩은 다형성이라는 개념을 구현할 수 있는 수단이자 방법이라고 볼 수 있다.

 

 

마치며

위의 내용을 정리하자면, 오버로딩과 오버라이딩을 비교하는 표를 살펴봤고, 오버로딩이 한 클래스 안이 아닌 상속 관계에서도 일어날 수 있다는 점, 그리고 정적 바인딩과 동적 바인딩에 대해 알아보고 마지막으로 다형성에 대해 알아봤다.

 

오버로딩과 오버라이딩은 메서드 이름을 재사용하고, 코드의 가독성을 향상시키며 다형성을 구현한다는 점에서 공통점을 보이지만, 사용 목적, 바인딩 타입, 메서드 이름을 제외한 문법적인 요소에서 차이를 보이고 있다.

 

공통점으로 메서드 이름이라는 문법적인 요소를 제외하면 다형성을 구현한다는 것 하나 뿐이기에 누군가는 이 둘은 단순히 이름이 비슷해서 비교당할 뿐이라고 생각할 수도 있고, 아니면 다형성에 크게 중점을 두어 기능이나 역할을 어느정도 비슷하다고 보고 비교된다고 생각할 수 있다.

 

필자는 전자쪽으로 생각이 기운다. 다형성이라는 공통점이 있지만 그 다형성을 더 세분화해보면 오버로딩은 메서드의 다양성을 제공하는 반면, 오버라이딩은 상속받은 메서드의 동작을 변경하여 클래스의 행위를 다양화하는 기능을 수행하기 때문이다. 즉, 엄연히 다른 역할을 담당한다. 또한 차이점 역시 상당하는 점도 참고해볼 수 있다.

 

오버로딩과 오버라이딩의 공통점과 차이점을 명확하게 인지하고 각 역할에 맡게 잘 사용하도록 하자.