오버로딩과 오버라이딩. 이름이 이렇게까지 비슷할 일인가 싶은 두 개념입니다.
시험과 면접의 단골손님인데, 헷갈리는 이유는 이름 탓이 큽니다. 동작 원리는 사실 완전히 다르거든요. 하나는 “같은 이름의 함수를 여러 벌 만드는 것”이고, 다른 하나는 “물려받은 함수를 다시 정의하는 것”입니다.
결정되는 시점도 달라요. 하나는 컴파일 타임, 하나는 런타임입니다. 이 차이가 정적 디스패치·동적 디스패치라는 성능 이야기로까지 이어지죠.
이 글에서 Swift 예제로 확실히 갈라 두겠습니다.
핵심 요약입니다.
- 오버로딩(Overloading): 같은 이름, 다른 매개변수의 함수를 여러 벌 정의. 상속과 무관
- 오버라이딩(Overriding): 부모의 메서드를 자식이 재정의. 상속이 전제
- 오버로딩은 컴파일 타임에, 오버라이딩은 런타임에 어떤 함수를 부를지 결정된다
- 오버라이딩이 바로 다형성의 핵심 메커니즘
오버로딩: 같은 이름을 여러 벌
오버로딩은 이름은 같고 매개변수 구성(개수·타입·레이블)이 다른 함수를 여러 개 정의하는 것입니다.
func area(radius: Double) -> Double { .pi * radius * radius }
func area(width: Double, height: Double) -> Double { width * height }
func area(side: Double) -> Double { side * side }
호출부에서 넘긴 인자를 보고 컴파일러가 어떤 함수인지 결정합니다. area(radius: 3)이라고 쓰는 순간 첫 번째 함수로 확정이에요. 실행하기 전에 이미 정해집니다.
이게 없던 C 언어를 떠올리면 고마움이 보입니다. 절댓값 함수가 abs, labs, fabs로 타입마다 이름이 달랐어요. 오버로딩은 “개념이 같으면 이름도 같게” 해 주는 장치입니다.
Swift는 print(:), min(:_:) 같은 표준 라이브러리 곳곳에서 오버로딩을 씁니다. 연산자 오버로딩으로 + 하나가 정수 덧셈, 문자열 연결, 배열 결합을 모두 처리하기도 하고요.
주의할 점 하나. 리턴 타입만 다른 오버로딩은 피하는 게 좋습니다. Swift에선 문법적으로 가능한 경우가 있지만 호출부에 타입 명시가 없으면 모호성 에러가 나고 읽는 사람도 헷갈립니다.
오버라이딩: 물려받은 걸 다시 정의
오버라이딩은 부모 클래스에서 물려받은 메서드를 자식 클래스가 같은 시그니처로 재정의하는 것입니다. 상속 관계가 전제예요.
class Animal {
func speak() { print("...") }
}
class Dog: Animal {
override func speak() { print("멍멍") }
}
class Cat: Animal {
override func speak() { print("야옹") }
}
핵심은 이 장면입니다.
let animals: [Animal] = [Dog(), Cat()]
for animal in animals {
animal.speak() // 멍멍, 야옹
}
변수 타입은 Animal인데, 실행되는 건 실제 인스턴스의 메서드입니다. “어떤 speak를 부를까”가 런타임에 실제 객체를 보고 결정되는 거예요. 이게 동적 디스패치이고 다형성이 동작하는 메커니즘입니다.
Swift가 override 키워드를 강제하는 것도 짚을 만합니다. 실수로 부모 메서드와 같은 시그니처를 만들거나, 오타로 재정의에 실패하는 사고를 컴파일러가 잡아주죠. 반대로 부모 쪽에서 final을 붙이면 재정의를 금지할 수 있습니다.
표로 한 번에 비교
| 구분 | 오버로딩 | 오버라이딩 |
|---|---|---|
| 정의 | 같은 이름, 다른 매개변수 | 같은 시그니처를 재정의 |
| 상속 | 필요 없음 | 필수 |
| 결정 시점 | 컴파일 타임 (정적) | 런타임 (동적) |
| 목적 | 이름의 일관성, API 편의 | 다형성, 행동 교체 |
| Swift 키워드 | 없음 | override (필수), final로 금지 |
결정 시점 차이는 성능과도 연결됩니다. 컴파일 타임에 확정되는 호출(정적 디스패치)은 바로 점프하면 되지만 런타임 결정(동적 디스패치)은 실제 타입의 메서드 테이블을 거쳐야 해요. Swift에서 final·private를 붙이면 컴파일러가 동적 디스패치를 정적으로 바꿔 최적화할 여지가 생깁니다.
헷갈리지 않는 암기법
영단어 뜻 그대로 기억하면 됩니다.
Overload = 과적. 같은 이름 위에 함수를 여러 벌 “싣는” 겁니다. 이름 하나에 기능 여러 개.
Override = 무시하고 밟고 지나가기. 부모의 정의를 “덮어쓰는” 겁니다. 정의는 하나, 내용을 교체.
면접 답은 이렇게 시작하면 됩니다.
“오버로딩은 매개변수가 다른 같은 이름의 함수를 여러 개 두는 것으로 컴파일 타임에 결정되고, 오버라이딩은 상속받은 메서드를 재정의하는 것으로 런타임에 실제 타입 기준으로 결정됩니다. 다형성을 만드는 건 오버라이딩 쪽입니다.”
정리
- 오버로딩: 같은 이름, 다른 매개변수 구성의 함수를 여러 벌 정의. 상속 불필요
- 오버라이딩: 부모 메서드를 자식이 같은 시그니처로 재정의. 상속 필수
- 오버로딩은 컴파일 타임(정적), 오버라이딩은 런타임(동적)에 호출 대상이 결정된다
- 다형성의 핵심 메커니즘은 오버라이딩과 동적 디스패치다
- Swift는 override 키워드 강제로 실수를 막고, final로 재정의를 금지할 수 있다
- 암기법: Overload는 이름에 과적, Override는 정의를 덮어쓰기
