같은 이름의 메서드를 매개변수를 달리해 여러 개 정의하는 것(overloading).
class Printer {
void print(int x) { }
void print(String s) { }
void print(int x, int y) { }
void print(double d) { }
}
성립 조건
매개변수의 타입·개수·순서 중 하나 이상이 달라야 한다.
void f(int x) { }
int f(int x) { return 0; } // ✗ 컴파일 에러 — 반환 타입만 다른 것은 안 됨
왜 반환 타입은 안 되나 — 호출부에서 반환값을 안 쓸 수 있기 때문이다.
f(1); // 반환값을 버린다면 어느 메서드를 부르는지 결정할 수 없다
매개변수 이름만 다른 것도 당연히 안 된다.
오버라이딩과의 결정적 차이 — 언제 결정되는가
| 오버로딩 | 오버라이딩 | |
|---|---|---|
| 관계 | 같은 클래스 안 | 상속 관계 |
| 시그니처 | 다르다 | 같다 |
| 결정 시점 | 컴파일 타임 (정적 바인딩) | 런타임 (동적 바인딩) |
| 기준 | 참조 변수의 타입 | 실제 객체의 타입 |
이 차이가 실제 동작 차이를 만든다.
class Parent {}
class Child extends Parent {}
void f(Parent p) { System.out.println("Parent"); }
void f(Child c) { System.out.println("Child"); }
Parent obj = new Child();
f(obj); // 출력: "Parent" ← 실제 객체는 Child인데!
컴파일러는 변수 obj의 선언 타입(Parent)만 보고 결정한다.
런타임의 실제 타입은 고려하지 않는다. 오버라이딩과 정반대다.
어느 것이 선택되나 — 우선순위
정확히 일치하는 것이 없으면 컴파일러가 단계적으로 넓혀 간다.
- ① 정확히 일치
- ② 기본형 확대 변환 (int → long → float → double)
- ③ 오토박싱 (int → Integer)
- ④ 가변인자 (int...)
void m(long x) { }
void m(Integer x) { }
void m(int... x) { }
m(10); // ② long 이 선택된다. 박싱보다 확대 변환이 우선
규칙 — 박싱은 확대 변환보다 나중이고, 가변인자는 가장 마지막이다. 자바 5에서 박싱과 가변인자를 추가하면서 기존 코드의 동작을 바꾸지 않기 위해 이 순서로 정한 것이다.
null을 넘기면
void m(String s) { }
void m(Object o) { }
m(null); // String 이 선택된다 — 더 구체적인(하위) 타입 우선
두 후보가 상속 관계가 아니면 모호하다는 컴파일 에러가 난다.
실무 주의점
① 타입에 따라 다르게 동작하게 만들지 않는다
list.remove(1); // 인덱스 1 제거
list.remove(Integer.valueOf(1)); // 값 1 제거
List.remove(int)와 remove(Object)가 오버로딩되어 있어
의도와 다른 것이 불릴 수 있다. 자바 컬렉션 API의 유명한 함정이다.
② 오버로딩보다 이름을 다르게 짓는 편이 나을 때가 많다
❌ save(User), save(Order), save(Product)
✅ saveUser, saveOrder, saveProduct // 의도가 분명하다
③ 매개변수가 많으면 빌더 패턴을 고려한다.