본문으로 건너뛰기

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 편의성, 직관적인 메서드 이름 사용