본문 바로가기
Programming/JAVA

JAVA 메소드와 생성자 알아가기2

by Henson Dev 2022. 2. 15.

메소드 시그니처(method signature)

메소드 오버로딩의 핵심은 바로 메소드 시그니처(method signature)에 있습니다.
메소드 시그니처란 메소드의 선언부에 명시되는 매개변수의 리스트를 가리킵니다.

만약 두 메소드가 매개변수의 개수와 타입, 그 순서까지 모두 같다면, 이 두 메소드의 시그니처는 같다고 할 수 있습니다.

 

메소드 오버로딩(method overloading)

메소드 오버로딩(overloading)이란 같은 이름의 메소드를 중복하여 정의하는 것을 의미합니다.
자바에서는 원래 한 클래스 내에 같은 이름의 메소드를 둘 이상 가질 수 없습니다.

하지만 매개변수의 개수나 타입을 다르게 하면, 하나의 이름으로 메소드를 작성할 수 있습니다.
즉, 메소드 오버로딩은 서로 다른 시그니처를 갖는 여러 메소드를 같은 이름으로 정의하는 것이라고 할 수 있습니다.

이러한 메소드 오버로딩을 사용함으로써 메소드에 사용되는 이름을 절약할 수 있습니다.
또한, 메소드를 호출할 때 전달해야 할 매개변수의 타입이나 개수에 대해 크게 신경을 쓰지 않고 호출할 수 있게 됩니다.
메소드 오버로딩은 객체 지향 프로그래밍의 특징 중 하나인 다형성(polymorphism)을 구현하는 방법 중 하나입니다.

메소드 오버로딩의 대표적인 예로는 println() 메소드를 들 수 있습니다.
println() 메소드는 전달받는 매개변수의 타입에 따라 다음과 같이 다양한 원형 중에서 적절한 원형을 호출하게 됩니다.

메소드 원형

1. println()
2. println(boolean x)
3. println(char x)
4. println(char[] x)
5. println(double x)
6. println(float x)
7. println(int x)
8. println(long x)
9. println(Object x)
10. println(String x)

 

 

메소드 오버로딩의 조건

자바에서 메소드 오버로딩이 성립하기 위해서는 다음과 같은 조건을 만족해야 합니다.

1. 메소드의 이름이 같아야 합니다.
2. 메소드의 시그니처, 즉 매개변수의 개수 또는 타입이 달라야 합니다.

메소드 오버로딩은 반환 타입과는 관계가 없습니다.
만약 메소드의 시그니처는 같은데 반환 타입만이 다른 경우에는 오버로딩이 성립하지 않습니다.

 

메소드 오버로딩의 예제

자바 컴파일러는 사용자가 오버로딩된 함수를 호출하면, 전달된 매개변수의 개수와 타입과 같은 시그니처를 가지는 메소드를 찾아 호출합니다.

다음 예제는 함수의 오버로딩을 이용하여 정의한 display() 메소드의 원형 예제입니다.

메소드의 원형 예제

1. void display(int num1)              // 전달받은 num1을 그대로 출력함.
2. void display(int num1, int num2)    // 전달받은 두 정수의 곱을 출력함.
3. void display(int num1, double num2) // 전달받은 정수와 실수의 합을 출력함.

 

이제 사용자가 display() 메소드를 호출하면, 컴파일러는 자동으로 같은 시그니처를 가지는 메소드를 찾아 호출합니다.

함수의 호출 예제

1. display(10);       // 1번 display() 메소드 호출 -> 10
2. display(10, 20);   // 2번 display() 메소드 호출 -> 200
3. display(10, 3.14); // 3번 display() 메소드 호출 -> 13.14
4. display(10, 'a');  // 2번과 3번 모두 호출 가능

 

문제는 4번과 같이 첫 번째 인수로는 정수를, 두 번째 인수로는 실수를 전달받는 호출입니다.
자바에서 char형 데이터는 int형 뿐만 아니라 double형으로도 타입 변환될 수 있기 때문입니다.

따라서 이와 같은 호출은 자바 컴파일러가 어느 시그니처의 display() 메소드를 호출해야 할지 불명확합니다.
자바에서는 오버로딩한 메소드의 이러한 모호한 호출을 허용하지 않으며, 위와 같은 경우에는 더 작은 표현 범위를 가지는 int형으로 자동 타입 변환하게 됩니다.

다음 예제는 위에서 살펴본 display() 메소드를 다양한 시그니처로 오버로딩하는 예제입니다.

예제

class Test {
    static void display(int num1) { System.out.println(num1); }
①  static void display(int num1, int num2) { System.out.println(num1 * num2); }
    static void display(int num1, double num2) { System.out.println(num1 + num2); }
}
public class Method06 {
    public static void main(String[] args) {
        Test myfunc = new Test();
        myfunc.display(10);
        myfunc.display(10, 20);
        myfunc.display(10, 3.14);
②      myfunc.display(10, 'a');
    }
}

 

실행 결과

10
200
13.14
970

 

위의 예제처럼 메소드의 오버로딩은 매개변수의 타입뿐만 아니라 매개변수의 개수를 달리해도 작성할 수 있습니다.
위 예제의 ②번 라인의 display() 메소드 호출은 영문 소문자 'a'의 아스키 코드값이 97이므로, int형으로 자동 타입 변환되어 ①번 라인의 display() 메소드가 호출될 것입니다.

 

재귀 호출(recursive call)

재귀 호출(recursive call)이란 메소드 내부에서 해당 메소드가 또다시 호출되는 것을 의미합니다.
이러한 재귀 호출은 자기가 자신을 계속해서 호출하므로, 끝없이 반복될 것입니다.
따라서 메소드 내에 재귀 호출을 중단하도록 조건이 변경될 명령문을 반드시 포함해야 합니다.

프로그래밍을 처음 접하는 사람들은 이러한 재귀 호출이 왜 필요한가에 대해 이해하기 힘들 수도 있습니다.
하지만 재귀 호출은 알고리즘이나 자료 구조론에서는 매우 중요한 개념 중 하나입니다.
또한, 재귀 호출을 사용하면 복잡한 문제도 매우 간단하게 논리적으로 접근하여 표현할 수 있습니다.

 

재귀 호출의 개념

재귀 호출의 개념을 파악하기 위해서 우선 재귀 호출을 사용하지 않고 1부터 n까지의 합을 구하는 메소드를 만들어 봅시다.

예제

int sum(int n) {
    int result = 0;
    for (int i = 1; i <= n; i++) {
        result += i;
    }
    return result;
}

 

위의 예제에서 sum() 메소드는 재귀 호출을 사용하지 않고 만든 메소드입니다.
이러한 메소드는 그냥 봐서는 그 목적을 바로 알 수 없으며, 코드를 해석해야 무슨 목적으로 만든 메소드인지 알 수 있습니다.
즉 변수 i와 result는 왜 정의됐으며, for 문은 왜 사용되었는지 바로 알 수가 없습니다.

이제 재귀 호출을 사용하여 1부터 n까지의 합을 구하는 recursiveSum() 메소드를 만들어 봅시다.

우선 1부터 4까지의 합을 구하는 알고리즘을 생각해 봅시다.

1. 1부터 4까지의 합은 1부터 3까지의 합에 4를 더하면 됩니다.
2. 1부터 3까지의 합은 1부터 2까지의 합에 3을 더하면 됩니다.
3. 1부터 2까지의 합은 1부터 1까지의 합에 2를 더하면 됩니다.
4. 1부터 1까지의 합은 그냥 1입니다.

위의 알고리즘을 의사 코드(pseudo code)로 작성하면 다음과 같습니다.

의사 코드

시작
    1. n이 1이 아니면, n과 1부터 (n-1)까지의 합을 더한 값을 반환함.
    2. n이 1이면, 그냥 1을 반환함.
끝

 

의사 코드(pseudo code)란 특정 프로그래밍 언어의 문법에 맞춰 작성된 것이 아닌, 일반적인 언어로 알고리즘을 표현한 코드를 의미합니다.

위와 같이 논리적인 재귀 알고리즘을 구상하고, 의사 코드를 작성하면, 재귀 호출을 이용해 바로 코드로 옮길 수 있습니다.

예제

int recursiveSum(int n) {
    if (n == 1) {                 // n이 1이면, 그냥 1을 반환함.
        return 1;
    }
    return n + recursiveSum(n-1); // n이 1이 아니면, n을 1부터 (n-1)까지의 합과 더한 값을 반환함.
}

 

실행 결과

55

 

위의 예제에서 if 문이 존재하지 않으면, 이 프로그램은 실행 직후 스택 오버플로우(stack overflow)에 의해 종료될 것입니다.
따라서 if 문처럼 재귀 호출을 중단하기 위한 조건문을 반드시 포함해야 합니다.

스택 오버플로우(stack overflow)는 메모리 구조 중 스택(stack) 영역에서 해당 프로그램이 사용할 수 있는 메모리 공간 이상을 사용하려고 할 때 발생합니다.

이처럼 재귀 호출은 다양한 알고리즘을 표현한 의사 코드를 그대로 코드로 옮길 수 있게 해주므로, 직관적인 프로그래밍을 하는 데 많은 도움을 줍니다.

'Programming > JAVA' 카테고리의 다른 글

JAVA 클래스 멤버 알가가기  (0) 2022.02.15
JAVA 제어자 알아가기  (0) 2022.02.15
JAVA 메소드와 생성자 알아가기 1  (0) 2022.02.15
JAVA 클래스 알아가기  (0) 2022.02.15
JAVA 배열 알아가기2  (0) 2022.02.14

댓글