Java - 오버로딩(overloading), 가변인자(varargs)

 

1. 오버로딩이란?

메서드도 변수와 마찬가지로 같은 클래스 내에서 서로 구분할 수 있어야 하기 때문에 서로 다른 이름을 가져야만 한다. 하지만 한 클래스 내에서 사용하려는 이름과 동일한 메서드가 존재하더라도 매개변수의 타입과 갯수가 다르다면 같은 이름의 메서드를 정의할 수 있다.

즉, 한 문장으로 정리해보면 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것메서드 오버로딩(method overloading) 또는 오버로딩(overloading) 이라고 한다.

2. 오버로딩의 조건

같은 이름의 메서드를 정의한다고 해서 무조건 오버로딩이 될 순 없다. 오버로딩이 성립되기 위해서는 아래와 같은 조건이 성립되어야 한다.

  • 메서드 이름이 같아야 한다.
  • 매개변수의 갯수, 타입이 달라야 한다.

그리고 주의해야 할 것은 반환타입은 오버로딩을 구현하는데 아무런 영향을 미치지 못한다.

3. 오버로딩의 예

오버로딩의 가장 대표적인 예는 printStream클래스의 println()이다. 아래의 코드에서처럼 어떤 종류의 매개변수를 지정하더라도 출력할 수 있게 10개의 오버로딩된 메서드를 정해놓고 있다. 그래서 매개변수에 어떤 인자를 지정하더라도 출력을 할 수 있는 것이다.

void println();
void println(boolean x);
void println(char x);
void println(char[] x);
void println(double x);
void println(float x);
void println(int x);
void println(long x);
void println(Object x);
void println(String x);

4. 오버로딩의 장점

그렇다면 오버로딩을 구현함으로써 얻는 이득은 무엇일까?

만약 오버로딩이 되지 않는다면 위에서 본 println()들이 매개변수에 따라 메서드의 이름이 아래의 코드처럼 달라지게 될 것이다. 이 메서드들은 근본적으로 동일한 기능을 수행하지만 서로 다른 이름을 가져야만 하기 때문에 메서드를 작성할 때 이름을 일일히 작성해줘야하는 번거로움이 생긴다. 그리고 메서드를 사용할 때도 매개변수에 따라 일일히 이름을 기억해야만 하는 불편함이 생긴다.

void println();
void printlnBoolean(boolean x);
void printlnChar(char x);
//...
void printlnLong(long x);
void printlnObject(Object x);
void printlnString(String x);

하지만 오버로딩을 함으로써 동일한 기능을 가진 여러 메서드들이 println()이라는 하나의 이름으로 정의될 수 있기때문에 메서드의 이름을 절약할 수 있다. 그리고 메서드를 사용하는 입장에서는 메서드의 이름이 동일하고, 매개변수가 다르기 때문에 같은 기능을 수행한다는 것을 유추할 수도 있다.

4.1 오버로딩 예제1 : 오버로딩 구현

지금까지 오버로딩에 대해서 간단히 정리해보았는데 예제를 통해 오버로딩을 구현해보자.

public class OverloadingTest {
    public static void main(String[] args) {

        MyMath3 myMath3 = new MyMath3();
        System.out.println("myMath3.add(3, 3) => result : " + myMath3.add(3, 3));
        System.out.println("myMath3.add(3L, 3) => result : " + myMath3.add(3L, 3));
        System.out.println("myMath3.add(3, 3L) => result : " + myMath3.add(3, 3L));
        System.out.println("myMath3.add(3L, 3L) => result : " + myMath3.add(3L, 3L));

        int[] a = {100, 200, 300};
        System.out.println("myMath3.add(a) => result : " + myMath3.add(a));
    }
}

class MyMath3 {

    int add(int a, int b) {
        System.out.print("int add(int a, int b) - ");
        return a + b;
    }

    long add(int a, long b) {
        System.out.print("long add(int a, long b) - ");
        return a + b;
    }

    long add(long a, int b) {
        System.out.print("long add(long a, int b) - ");
        return a + b;
    }

    long add(long a, long b) {
        System.out.print("long add(long a, long b) - ");
        return a + b;
    }

    int add(int[] a) {
        System.out.print("int add(int[] a) - ");
        int result = 0;
        for (int i = 0; i < a.length; i++) {
            result += a[i];
        }
        return result;
    }

}
int add(int a, int b) - myMath3.add(3, 3) => result : 6
long add(long a, int b) - myMath3.add(3L, 3) => result : 6
long add(int a, long b) - myMath3.add(3, 3L) => result : 6
long add(long a, long b) - myMath3.add(3L, 3L) => result : 6
int add(int[] a) - myMath3.add(a) => result : 600

5. 가변인자(varargas)와 오버로딩

기존에는 메서드의 매개변수의 갯수가 고정적이었지만, JDK1.5부터는 동적으로 매개변수를 지정해 줄 수 있게 가변인자라는 것이 생겼다.

Type methodName(Type... variableName) {
  //...
}

가변인자는 위와 같이 Type... variableName과 같은 형식으로 선언 하며, PrintStream클래스의 printf()가 대표적인 예이다. 만약 아래의 코드와 같이 가변인자 외에도 매개변수가 있다면, 가변인자는 가장 마지막에 선언해야한다. 그렇지 않으면 컴파일 에러가 발생하는데 가변인자인지 아닌지를 구분할 방법이 없기 때문이다.

// 가변인자는 항상 마지막에 선언, 그렇지 않으면 컴파일 에러 발생
public PrintStream printf(String format, Object... args) {
  // ...
}

5.1 가변인자 예제 1 : 여러문자 합치기

만약 여러 문자열을 하나로 결합하여 반환하는 기능을 가진 메서드 concatenate()를 작성한다면, 아래와 같이 작성을 해줘야 할 것이다.

String concatenate(String s1, String s2) {}
String concatenate(String s1, String s2, String s3) {}
String concatenate(String s1, String s2, String s3, String s4) {}

하지만 가변인자를 사용하면 위의 코드를 한줄로 줄일 수 있다.

String concatenate(String... str) {}

이 메서드를 호출할 때는 아래와 같이 인자의 갯수를 가변적으로 처리할 수 있다.

System.out.println(concatenate());  // 인자 없음
System.out.println(concatenate("a")); // 인자 1개
System.out.println(concatenate("a", "b")); // 인자 2개
System.out.println(concatenate(new String[]{"a", "b"})); // 인자 배열

그렇다면 가변인자와 매개변수의 타입을 배열로 선언하는 것과 차이는 무엇일까? 아래의 코드를 보면 배열타입의 매개변수의 경우 반드시 인자를 지정해줘야하기 때문에 위의 코드처럼 인자를 생략할 수가 없다. 그래서 null이나 0인 배열을 인자로 지정해줘야하는 불편함이 있다.

String concatenate(String[] str) {}
String result = concatenate(new String[0]); // 인자를 배열로 지정
String result = concatenate(null); // 인자를 null로 지정
String result = concatenate(); // 에러, 인자가 반드시 필요하다.

가변인자를 오버로딩할 때 주의해야할 점이 있는데, 아래의 예제를 통해 알아보자.

public class VarArgsEx {
    public static void main(String[] args) {
        String[] strArr = {"100", "200", "300"};
        System.out.println(concatenate("", "100", "200", "300"));
        System.out.println(concatenate("-", strArr));
        System.out.println(concatenate(",", new String[]{"1", "2", "3"}));
        System.out.println("["+concatenate(",", new String[0])+ "]");
        System.out.println("["+concatenate(",")+ "]");
    }

    static String concatenate(String delim, String... args) {
        String result = "";
        for (String str : args) {
            result += str + delim;
        }

        return result;
    }

//
//    static String concatenate(String... args) {
//        return concatenate("", args);
//    }
//
}
100200300
100-200-300-
1,2,3,
[]
[]

concatenate()는 매개변수로 입력된 문자열에 구분자를 결합하여 반환한다. 가변인자를 매개변수로 선언했기 때문에 문자열의 갯수의 제한없이 매개변수로 지정할 수 있다. 그리고 concatenate()의 또다른 메서드, 오버로딩된 메서드가 주석처리가 되어있는데 이 주석을 풀고 컴파일하면 아래와 같은 에러가 발생한다.

Error:(6, 28) java: reference to concatenate is ambiguous both method concatenate(java.lang.String,java.lang.String...)
in com.doubles.standardofjava.ch06_oop1.part04_overloading.VarArgsEx and method concatenate(java.lang.String...)
in com.doubles.standardofjava.ch06_oop1.part04_overloading.VarArgsEx match

에러의 내용을 살펴보면 두 오버로딩된 메서드가 구분되지 않아서 발생한 에러이다. 가변인자를 선언한 메서드를 오버로딩하면 메서드를 호출했을 때 위와 같이 구별되지 못하는 경우가 발생하기 쉽기때문에 주의를 해야한다. 가능하다면 가변인자를 사용한 메서드는 오버로딩을 하지 않는 것이 바람직하다.

6. Reference

본 내용은 자바의 정석 3rd Edition을 참고하여 작성되었습니다. 개인적으로 학습한 내용을 복습하기 목적이기 때문에 내용상 오류가 있을 수 있습니다.