6.4 메서드 오버로딩 (Method Overloading)
자바가 지원하는 강력한 기능 중 하나로, 같은 이름의 메서드를 여러 개 선언 할 수 있도록 허용하는 문법입니다. 매개변수를 다르게 구성하는 한, 같은 이름이어도 서로 다른 메서드로 인식됩니다.
1. 오버로딩이란?
일반적으로 한 클래스 내에서 식별자(이름)는 유일해야 합니다. 하지만 매개변수의 타입·개수·순서를 다르게 하면, 동일한 이름으로 여러 메서드를 선언할 수 있습니다. 이를 메서드 오버로딩(Method Overloading) 이라 부릅니다.
2. 오버로딩의 조건
| 조건 | 내용 |
|---|---|
| 메서드 이름 | 같아야 함 |
| 매개변수 타입 | 다르면 오버로딩 성립 |
| 매개변수 개수 | 다르면 오버로딩 성립 |
| 매개변수 순서 | 타입이 다른 경우 다른 순서도 성립 |
| 반환 타입 | 관계없음(반환 타입만 다른 것은 오버로딩 아님) |
class OverloadingTest {
// 1. 매개변수 개수가 다름 → 오버로딩 성립
int add(int a, int b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
// 2. 매개변수 타입이 다름 → 오버로딩 성립
double add(double a, double b) { return a + b; }
long add(long a, long b) { return a + b; }
// 3. 매개변수 순서가 다름 → 오버로딩 성립 (타입이 다른 경우)
void print(String s, int n) { System.out.println(s + ": " + n); }
void print(int n, String s) { System.out.println(n + " - " + s); }
// 오버로딩 불성립 예시 (컴파일 에러):
// int add(int a, int b) { ... } // 이미 있음 → 중복 에러
// double add(int a, int b) { ... } // 반환타입만 다름 → 오버로딩 아님
}
반환 타입만 다른 메서드는 오버로딩이 아닙니다. 컴파일러는 메서드를 호출할 때 메서드명과 매개변수만으로 어떤 메서드를 호출할지 결정하기 때문에, 반환 타입으로는 구분할 수 없습니다.
3. System.out.println()이 오버로딩의 대표 사례
System.out.println()에 어떤 타입의 값이든 넣어도 동작하는 이유는, println 메서드가 수십 개의 타입별로 오버로딩되어 있기 때문입니다.
// Java 표준 라이브러리 PrintStream 클래스 내부 (개념적 예시)
public class PrintStream {
public void println(boolean x) { ... }
public void println(char x) { ... }
public void println(int x) { ... }
public void println(long x) { ... }
public void println(float x) { ... }
public void println(double x) { ... }
public void println(String x) { ... }
public void println(Object x) { ... }
// ... 10개 이상 오버로딩됨
}
// 덕분에 우리는 타입별로 다른 이름을 외울 필요가 없음
System.out.println(42); // int 버전 호출
System.out.println(3.14); // double 버전 호출
System.out.println("자바"); // String 버전 호출
System.out.println(true); // boolean 버전 호출
4. 오버로딩 vs 오버라이딩 비교
두 개념은 이름이 비슷하지만 완전히 다릅니다.
| 구분 | 오버로딩 (Overloading) | 오버라이딩 (Overriding) |
|---|---|---|
| 발생 위치 | 같은 클래스 내 | 상속 관계의 자식 클래스 |
| 메서드 이름 | 같음 | 같음 |
| 매개변수 | 다름 | 완전히 같아야 함 |
| 반환 타입 | 달라도 됨 | 같아야 함 (공변 반환 가능) |
| 결정 시점 | 컴파일 타임(정적 바인딩) | 런타임(동적 바인딩) |
| 키워드 | 없음 | @Override 어노테이션 권장 |
class Animal {
// 오버로딩: 같은 클래스에서 동일한 이름, 다른 매개변수
void sound() { System.out.println("..."); }
void sound(int times) { for(int i=0;i<times;i++) sound(); }
void sound(String prefix) { System.out.println(prefix + "..."); }
}
class Dog extends Animal {
// 오버라이딩: 부모 메서드를 재정의 (매개변수 동일)
@Override
void sound() { System.out.println("멍멍!"); }
}
5. 자동 형변환과 오버로딩의 상호작용
오버로딩된 메서드 중 정확히 일치하는 매개변수 타입이 없을 때, 자바는 자동 형변환을 적용하여 가장 가까운 타입의 메서드를 선택합니다.
public class AutoCastOverload {
static void print(int n) { System.out.println("int: " + n); }
static void print(long n) { System.out.println("long: " + n); }
static void print(double n) { System.out.println("double: " + n); }
public static void main(String[] args) {
print(10); // int: 10 (int → int, 정확히 일치)
print(10L); // long: 10 (long → long, 정확히 일치)
print(10.0); // double: 10.0 (double → double, 정확히 일치)
byte b = 5;
print(b); // int: 5 (byte → int로 자동 형변환, int가 가장 가까움)
float f = 3.14f;
print(f); // double: 3.14 (float → double로 자동 형변환)
}
}
byte → short → int → long → float → double 순으로 자동 형변환(승격, Promotion)이 일어납니다. 오버로딩 시 더 큰 타입의 메서드가 선택됩니다.
6. 가변인수(Varargs)와 오버로딩
Java 5부터 도입된 가변인수(Variable Arguments, Varargs) 를 사용하면 매개변수 개수가 가변적인 메서드를 작성할 수 있습니다.
public class VarargsExample {
// 가변인수: int... 은 int 배열과 동일하게 동작
static int sum(int... numbers) {
int total = 0;
for (int n : numbers) {
total += n;
}
return total;
}
// 가변인수와 고정 매개변수 혼합
static void log(String prefix, Object... messages) {
System.out.print("[" + prefix + "] ");
for (Object msg : messages) {
System.out.print(msg + " ");
}
System.out.println();
}
public static void main(String[] args) {
System.out.println(sum()); // 0 (인수 없음)
System.out.println(sum(1, 2, 3)); // 6
System.out.println(sum(1, 2, 3, 4, 5)); // 15
log("INFO", "서버 시작");
log("ERROR", "연결 실패", "코드:", 404);
log("DEBUG", "a=", 10, "b=", 20, "sum=", 30);
}
}
- 가변인수는 메서드당 하나만, 마지막 매개변수로만 선언 가능합니다.
- 가변인수 메서드와 배열 매개변수 메서드는 오버로딩 충돌이 발생할 수 있습니다.
7. 오버로딩의 장점: API 편의성
오버로딩은 라이브러리나 API 설계에서 특히 빛을 발합니다. 사용자가 다양한 입력 형태로 동일한 기능을 쉽게 사용할 수 있게 합니다.
8. 실전 예제: Logger 클래스
실제 프로젝트에서 유용한 Logger 클래스를 오버로딩으로 구현합니다.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Logger {
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private String prefix;
private boolean enabled;
public Logger(String prefix) {
this.prefix = prefix;
this.enabled = true;
}
// 오버로딩 1: 메시지만
public void log(String message) {
print("INFO", message);
}
// 오버로딩 2: 레벨 + 메시지
public void log(String level, String message) {
print(level, message);
}
// 오버로딩 3: 메시지 + 숫자 데이터
public void log(String message, int value) {
print("INFO", message + " = " + value);
}
// 오버로딩 4: 메시지 + double 데이터
public void log(String message, double value) {
print("INFO", String.format("%s = %.2f", message, value));
}
// 오버로딩 5: 예외 로깅
public void log(String message, Exception e) {
print("ERROR", message + " [" + e.getClass().getSimpleName() + ": " + e.getMessage() + "]");
}
// 오버로딩 6: 포맷 문자열
public void log(String format, Object... args) {
print("INFO", String.format(format, args));
}
private void print(String level, String message) {
if (!enabled) return;
String time = LocalDateTime.now().format(FORMATTER);
System.out.printf("[%s][%s][%s] %s%n", time, level, prefix, message);
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
public class LoggerTest {
public static void main(String[] args) {
Logger logger = new Logger("MyApp");
// 오버로딩된 다양한 log() 호출
logger.log("서버가 시작되었습니다");
logger.log("WARN", "메모리 사용량이 높습니다");
logger.log("현재 연결 수", 42);
logger.log("CPU 사용률", 78.5);
logger.log("사용자 수: %d명, 서버: %s", 1500, "prod-01");
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
logger.log("계산 오류 발생", e);
}
}
}
출력 예시:
[2025-01-01 12:00:00][INFO][MyApp] 서버가 시작되었습니다
[2025-01-01 12:00:00][WARN][MyApp] 메모리 사용량이 높습니다
[2025-01-01 12:00:00][INFO][MyApp] 현재 연결 수 = 42
[2025-01-01 12:00:00][INFO][MyApp] CPU 사용률 = 78.50
[2025-01-01 12:00:00][INFO][MyApp] 사용자 수: 1500명, 서버: prod-01
[2025-01-01 12:00:00][ERROR][MyApp] 계산 오류 발생 [ArithmeticException: / by zero]
9. 생성자 오버로딩 활용
public class Rectangle {
private double width;
private double height;
// 정사각형: 한 변의 길이
public Rectangle(double side) {
this(side, side);
}
// 직사각형: 가로 × 세로
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
// 0×0 기본 사각형
public Rectangle() {
this(0, 0);
}
public double area() { return width * height; }
public double perimeter() { return 2 * (width + height); }
@Override
public String toString() {
return String.format("Rectangle(%.1f × %.1f)", width, height);
}
}
public class RectangleTest {
public static void main(String[] args) {
Rectangle square = new Rectangle(5); // 정사각형
Rectangle rect = new Rectangle(4, 7); // 직사각형
Rectangle empty = new Rectangle(); // 기본
System.out.println(square + " 넓이: " + square.area()); // 25.0
System.out.println(rect + " 넓이: " + rect.area()); // 28.0
System.out.println(empty + " 넓이: " + empty.area()); // 0.0
}
}
요약
| 개념 | 핵심 내용 |
|---|---|
| 오버로딩 | 같은 이름, 다른 매개변수 (타입/개수/순서) |
| 오버로딩 조건 | 매개변수가 달라야 함 (반환 타입은 관계없음) |
| 대표 예시 | System.out.println() |
| 오버로딩 vs 오버라이딩 | 컴파일타임 vs 런타임 결정 |
| 자동 형변환 | 정확한 타입 없으면 더 큰 타입의 메서드 선택 |
| 가변인수 | 타입... 형식, 마지막 매개변수에만 사용 |
| 장점 | API 편의성, 직관적인 메서드 이름 사용 |