Java 공부 11일차(메서드란?)2편

임선구·2025년 1월 8일

몸 비틀며 Java

목록 보기
12/58

오늘의 잔디


오늘의 공부



메서드와 형변환

메서드를 호출할 때도 형변환이 적용된다. 메서드 호출과 명시적 형변환, 자동 형변환에 대해 알아보자.

명시적 형변환

메서드를 호출하는데 인자와 매개변수의 타입이 맞지 않다면 어떻게 해야할까?

다음 예제 코드를 보자

MethodCasting1

package method;
public class MethodCasting1 {
 public static void main(String[] args) {
 double number = 1.5;
 //printNumber(number); // double을 int형에 대입하므로 컴파일 오류
 printNumber((int) number); // 명시적 형변환을 사용해 double을 int로 변환
 }
 public static void printNumber(int n) {
 System.out.println("숫자: " + n);
 }
}

먼저 주석으로 처리해둔 부분의 주석을 풀고 실행해보자.

printNumber(number) // double을 int형에 대입하므로 컴파일 오류

실행 결과 - 컴파일 오류

다음과 같은 이유로 컴파일 오류가 발생한다.

printNumber(number) //number는 1.5 실수
printNumber(1.5) //메서드를 호출하기 전에 number 변수의 값을 읽음
void printNumber(int n=1.5) //int형 매개변수 n에 double형 실수인 1.5를 대입 시도, 컴파일 오류

이 경우 메서드 호출이 꼭 필요하다면 다음과 같이 명시적 형변환을 사용해야 한다.

printNumber((int) number); // 명시적 형변환을 사용해 double을 int로 변환
printNumber(1); // (double) 1.5 -> (int) 1로 변환
void printNumber(int n=1) //int형 파라미터 변수 n에 int형 1을 대입 

실행 결과

숫자: 1

자동 형변환

int < long < double
메서드를 호출할 때 매개변수에 값을 전달하는 것도 결국 변수에 값을 대입하는 것이다. 따라서 앞서 배운 자동 형변환이 그대로 적용된다.

package method;
public class MethodCasting2 {
 public static void main(String[] args) {
 int number = 100;
 printNumber(number); // int에서 double로 자동 형변환
 }
 public static void printNumber(double n) {
 System.out.println("숫자: " + n);
 }
}
  • double 형 매개변수(파라미터)에 int 형 인수를 전달하는데 문제없이 잘 동작한다.

실행 결과

숫자: 100.0 

다음과 같이 자동 형변환이 동작한다.

printNumber(number); // number는 int형 100
printNumber(100); //메서드를 호출하기 전에 number 변수의 값을 읽음
void printNumber(double n=100) //double형 파라미터 변수 n에 int형 값 100을 대입
void printNumber(double n=(double) 100) //double이 더 큰 숫자 범위이므로 자동 형변환 적용
void printNumber(double n=100.0) //자동 형변환 완료 

정리
메서드를 호출할 때는 전달하는 인수의 타입과 매개변수의 타입이 맞아야 한다. 단 타입이 달라도 자동 형변환이 가능한 경우에는 호출할 수 있다.

메서드 오버로딩

다음과 같은 메서드를 만들고 싶다.

  • 두 수를 더하는 메서드
  • 세 수를 더하는 메서드

이 경우 둘다 더하는 메서드이기 때문에 가급적 같은 이름인 add 를 사용하고 싶다.
자바는 메서드의 이름 뿐만 아니라 매개변수 정보를 함께 사용해서 메서드를 구분한다.
따라서 다음과 같이 이름이 같고, 매개변수가 다른 메서드를 정의할 수 있다.

오버로딩 성공

add(int a, int b)
add(int a, int b, int c)
add(double a, double b) 

이렇게 이름이 같고 매개변수가 다른 메서드를 여러개 정의하는 것을 메서드 오버로딩(Overloading)이라 한다.
오버로딩은 번역하면 과적인데, 과하게 물건을 담았다는 뜻이다. 따라서 같은 이름의 메서드를 여러개 정의했다고 이해
하면 된다.

오버로딩 규칙
메서드의 이름이 같아도 매개변수의 타입 및 순서가 다르면 오버로딩을 할 수 있다. 참고로 반환 타입은 인정하지 않는
다.다음 케이스는 메서드 이름과 매개변수의 타입이 같으므로 컴파일 오류가 발생한다. 반환 타입은 인정하지 않는다.

오버로딩 실패

int add(int a, int b)
double add(int a, int b) 

용어: 메서드 시그니처(method signature)
메서드 시그니처 = 메서드 이름 + 매개변수 타입(순서)
메서드 시그니처는 자바에서 메서드를 구분할 수 있는 고유한 식별자나 서명을 뜻한다. 메서드 시그니처는 메서드의 이
름과 매개변수 타입(순서 포함)으로 구성되어 있다. 쉽게 이야기해서 메서드를 구분할 수 있는 기준이다. 자바 입장에서
는 각각의 메서드를 고유하게 구분할 수 있어야한다. 그래야 어떤 메서드를 호출 할 지 결정할 수 있다.
따라서 메서드 오버로딩에서 설명한 것 처럼 메서드 이름이 같아도 메서드 시그니처가 다르면 다른 메서드로 간주한다.
반환 타입은 시그니처에 포함되지 않는다. 방금 오버로딩이 실패한 두 메서드를 보자. 두 메서드는 add(int a, int b) 로 메서드 시그니처가 같다. 따라서 메서드의 구분이 불가능하므로 컴파일 오류가 발생한다.

다양한 예제를 통해서 메서드 오버로딩을 알아보자.

먼저 매개변수의 갯수가 다른 오버로딩 예제를 보자
Overloading1

package overloading;
public class Overloading1 {
 public static void main(String[] args) {
 System.out.println("1: " + add(1, 2));
 System.out.println("2: " + add(1, 2, 3));
 }
 // 첫 번째 add 메서드: 두 정수를 받아서 합을 반환한다.
 public static int add(int a, int b) {
 System.out.println("1번 호출");
 return a + b;
 }
 // 두 번째 add 메서드: 세 정수를 받아서 합을 반환한다.
 // 첫 번째 메서드와 이름은 같지만, 매개변수 목록이 다르다.
 public static int add(int a, int b, int c) {
 System.out.println("2번 호출");
 return a + b + c;
 }}

1: 정수 1,2를 호출했으므로 add(int a, int b) 가 호출된다.
2: 정수 1,2,3을 호출했으므로 add(int a, int b, int c) 가 호출된다.

실행 결과

1번 호출
1: 3
2번 호출
2: 6

이번에는 매개변수의 타입이 다른 오버로딩 예제를 보자
Overloading2

package overloading;
public class Overloading2 {
 public static void main(String[] args) {
 myMethod(1, 1.2);
 myMethod(1.2, 2);
 }
 public static void myMethod(int a, double b) {
 System.out.println("int a, double b");
 }
 public static void myMethod(double a, int b) {
 System.out.println("double a, int b");
 }
}

1: 정수1, 실수 1.2를 호출했으므로 myMethod(int a, double b) 가 호출된다.
2: 실수 1.2, 정수 2를 호출했으므로 myMethod(double a, int b) 가 호출된다.

실행 결과

int a, double b
double a, int b

마지막으로 매개변수의 타입이 다른 경우를 추가로 확인해보자.
Overloading3

package overloading;
public class Overloading3 {
 public static void main(String[] args) {
 System.out.println("1: " + add(1, 2));
 System.out.println("2: " + add(1.2, 1.5));
 }
 // 첫 번째 add 메서드: 두 정수를 받아서 합을 반환한다.
 public static int add(int a, int b) {
 System.out.println("1번 호출");
 return a + b;
 }
 // 두 번째 add 메서드: 두 실수를 받아서 합을 반환한다.
 // 첫 번째 메서드와 이름은 같지만, 매개변수의 유형이 다르다.
 public static double add(double a, double b) {
 System.out.println("2번 호출");
 return a + b;
 }
}

1: 정수1, 정수 2를 호출했으므로 add(int a, int b) 가 호출된다.
2: 실수 1.2, 실수 1.5를 호출했으므로 add(double a, double b) 가 호출된다.

실행 결과

1번 호출
1: 3
2번 호출
2: 2.7

여기서 만약 다음 첫 번째 메서드를 삭제하면 어떻게 될까?

public static int add(int a, int b) {
 System.out.println("1번 호출");
 return a + b;
}

1: int 형 정수 1, int 형 정수 2를 호출했으므로 자동 형변환이 발생해서 add(double a, double b) 가 호출된다.
2: 실수 1.2, 실수 1.5를 호출했으므로 add(double a, double b) 가 호출된다.

실행 결과

2번 호출
1: 3.0
2번 호출
2: 2.7

정리하면 먼저 본인의 타입에 최대한 맞는 메서드를 찾아서 실행하고, 그래도 없으면 형 변환 가능한 타입의 메서드를
찾아서 실행한다.


문제풀이

메서드 추출 리펙토링

본좌가 오늘 푼 문제

main문만 읽어도 무슨 프로그램인지 알 수 있게 하는게 좋은 코드

리펙토링 결과를 보면 main() 은 세세한 코드가 아니라 전체 구조를 한눈에 볼 수 있게 되었다. 쉽게 이야기해서 책의목차를 보는 것 같다. 더 자세히 알고 싶으면 해당 메서드를 찾아서 들어가면 된다. 그리고 입금과 출금 부분이 메서드로 명확하게 분리되었기 때문에 이후에 변경 사항이 발생하면 관련된 메서드만 수정하면 된다. 특정 메서드로 수정 범위가 한정되기 때문에 더 유지보수 하기 좋다.
이런 리펙토링을 메서드 추출(Extract Method)이라 한다. 메서드를 재사용하는 목적이 아니어도 괜찮다. 메서드를 적
절하게 사용해서 분류하면 구조적으로 읽기 쉽고 유지보수 하기 좋은 코드를 만들 수 있다.
그리고 메서드의 이름 덕분에 프로그램을 더 읽기 좋게 만들 수 있다.

package method.ex;
public class MethodEx3Ref {

    public static void main(String[] args) 
    {
        int balance = 10000;

        // 원금 10000원, 입금 1000
        balance = deposit(balance,1000);

        // 출금 2000
        balance = withdraw(balance, 2000);

        System.out.println("최종 잔액: " + balance + "원");
    }

    public static int deposit(int balance, int depositAmount)
    {
        balance += depositAmount;
        System.out.println(depositAmount + "원을 입금하였습니다. 현재 잔액: " + balance + "원");
        return balance;
    }

    public static int withdraw(int balance, int withdrawAmount)
    {
        if (balance >= withdrawAmount)
        {
            balance -= withdrawAmount;
            System.out.println(withdrawAmount + "원을 출금하였습니다. 현재 잔액: " + balance + "원");
        }

        else
        {
            System.out.println(withdrawAmount + "원을 출금하려 했으나 잔액이 부족합니다.");
        }

        return balance;
    }
}

문제와 풀이2

문제 - 은행 계좌 입출금

  • 다음 실행 예시를 참고해서, 사용자로부터 계속 입력을 받아 입금과 출금을 반복 수행하는 프로그램을 작성하자.
    또한 간단한 메뉴를 표시하여 어떤 동작을 수행해야 할지 선택할 수 있게 하자
  • 출금시 잔액이 부족하다면 "x원을 출금하려 했으나 잔액이 부족합니다."라고 출력해야 한다.

실행 예시

---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 1
입금액을 입력하세요: 10000
10000원을 입금하였습니다. 현재 잔액: 10000원
---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 2
출금액을 입력하세요: 8000
8000원을 출금하였습니다. 현재 잔액: 2000원
---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 2
출금액을 입력하세요: 3000
3000원을 출금하려 했으나 잔액이 부족합니다.
---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 3
현재 잔액: 2000원
---------------------------------
1.입금 | 2.출금 | 3.잔액 확인 | 4.종료
---------------------------------
선택: 4
시스템을 종료합니다.

풀이1

package method.ex;

import java.util.Scanner;

public class MethodEx4
{

    public static void main(String[] args)
    {
        Scanner scanner = new Scanner(System.in);
        int balance = 0; // 총액

        while (true)
        {
            System.out.println("---------------------------------------");
            System.out.println("1. 입금 | 2. 출금 | 3. 잔액 확인 | 4. 종료");
            System.out.println("---------------------------------------");
            System.out.print("선택: ");

            int choice = scanner.nextInt();
            int amount;

            if(choice == 1) // 입금
            {
                System.out.print("입금액을 입력하세요: ");
                amount = scanner.nextInt();
                balance = deposit(balance, amount);
            }

            else if(choice == 2) // 출금
            {
                System.out.print("출금액을 입력하세요: ");
                amount = scanner.nextInt();
                balance = withdraw(balance, amount);
            }

            else if(choice == 3) // 잔액 확인
            {
                System.out.println("현재 잔액: " + balance + "원");
            }

            else if(choice == 4) // 종료
            {
                System.out.println("시스템을 종료합니다.");
                break;
            }

            else
            {
                System.out.println("올바른 선택이 아닙니다. 다시 선택해주세요.");
            }
        }
    }

    public static int deposit(int balance, int amount)
    {
        balance += amount;
        System.out.println(amount + "원을 입금하였습니다. 현재 잔액: " + balance + "원");
        return balance;
    }

    public static int withdraw(int balance, int amount)
    {
        if (balance >= amount)
        {
            balance -= amount;
            System.out.println(amount + "원을 출금하였습니다. 현재 잔액: " + balance + "원");
        }

        else
        {
            System.out.println(amount + "원을 출금하려 했으나 잔액이 부족합니다.");
        }

        return balance;
    }
}

본좌가 if 문으로 푼 풀이

풀이2

import java.util.Scanner;
public class MethodEx4 {
 public static void main(String[] args) {
 int balance = 0;
 Scanner scanner = new Scanner(System.in);
 while (true) {
 System.out.println("---------------------------------");
 System.out.println("1.입금 | 2.출금 | 3.잔액 확인 | 4.종료");
 System.out.println("---------------------------------");
 System.out.print("선택: ");
 int choice = scanner.nextInt();
 int amount;
 switch (choice) {
 case 1:
 System.out.print("입금액을 입력하세요: ");
 amount = scanner.nextInt();
 balance = deposit(balance, amount);
 break;
 case 2:
 System.out.print("출금액을 입력하세요: ");
 amount = scanner.nextInt();
 balance = withdraw(balance, amount);
 break;
 case 3:
 System.out.println("현재 잔액: " + balance + "원");
 break;
 case 4:
 System.out.println("시스템을 종료합니다.");
 return;
 default:
 System.out.println("올바른 선택이 아닙니다. 다시 선택해주세요.");
 }
 }
 } public static int deposit(int balance, int amount) {
 balance += amount;
 System.out.println(amount + "원을 입금하였습니다. 현재 잔액: " + balance +
"원");
 return balance;
 }
 public static int withdraw(int balance, int amount) {
 if (balance >= amount) {
 balance -= amount;
 System.out.println(amount + "원을 출금하였습니다. 현재 잔액: " + balance +
"원");
 } else {
 System.out.println(amount + "원을 출금하려 했으나 잔액이 부족합니다.");
 }
 return balance;
 }
}

강사님이 푼 switch문 풀이

정리

변수명 vs 메서드명

변수 이름은 일반적으로 명사를 사용한다. 한편 메서드는 무언가 동작하는데 사용하기 때문에 일반적으로 동사로 시작
한다.
이런 차이점 외에는 변수 이름과 메서드 이름에 대한 규칙은 둘다 같다.

  • 변수명 예): customerName , totalSum , employeeCount , isAvailable
  • 메서드명 예): printReport() , calculateSum() , addCustomer() , getEmployeeCount() ,
    setEmployeeName()

메서드 사용의 장점

  • 코드 재사용: 메서드는 특정 기능을 캡슐화하므로, 필요할 때마다 그 기능을 다시 작성할 필요 없이 해당 메서드를 호출함으로써 코드를 재사용할 수 있다.
  • 코드의 가독성: 이름이 부여된 메서드는 코드가 수행하는 작업을 명확하게 나타내므로, 코드를 읽는 사람에게 추
    가적인 문맥을 제공한다.모듈성: 큰 프로그램을 작은, 관리 가능한 부분으로 나눌 수 있다. 이는 코드의 가독성을 향상시키고 디버깅을 쉽게 만든다.
  • 코드 유지 관리: 메서드를 사용하면, 코드의 특정 부분에서 문제가 발생하거나 업데이트가 필요한 경우 해당 메서드만 수정하면 된다. 이렇게 하면 전체 코드 베이스에 영향을 주지 않고 변경 사항을 적용할 수 있다.
  • 재사용성과 확장성: 잘 설계된 메서드는 다른 프로그램이나 프로젝트에서도 재사용할 수 있으며, 새로운 기능을
    추가하거나 기존 기능을 확장하는 데 유용하다.
  • 추상화: 메서드를 사용하는 곳에서는 메서드의 구현을 몰라도 된다. 프로그램의 다른 부분에서는 복잡한 내부 작업에 대해 알 필요 없이 메서드를 사용할 수 있다.
  • 테스트와 디버깅 용이성: 개별 메서드는 독립적으로 테스트하고 디버그할 수 있다. 이는 코드의 문제를 신속하게
    찾고 수정하는 데 도움이 된다.

따라서, 메서드는 효율적이고 유지 보수가 가능한 코드를 작성하는 데 매우 중요한 도구이다.

profile
끝까지 가면 내가 다 이겨

0개의 댓글