[Java] 직접 코딩하며 학습하기 - (1) 메서드

Eunbi Lee·2024년 2월 11일
1

Java

목록 보기
5/5
post-thumbnail

평소에 프로젝트를 할 때나 코딩 테스트를 풀 때, 아직 자바 문법에 대해 공백이 있다는 것을 깨닫게 되었다.

따라서 비어있는 개념을 채우기 위해 기본서를 2회독을 할지, 강의를 새로 들어볼지 고민하다가 마침 영한님의 자바 로드맵이 오픈되었다는 소식을 듣고!

강의로 결정하여 다음과 같은 포맷으로 학습을 진행하게 되었다.

  1. 평소에 조금이라도 싶었던 부분 위주로 강의로 1회독
  2. 이때, PDF와 코드를 참고하며 학습
  3. 강의를 시청한 후, 문제를 풀며 개념 복기
  4. 블로그에 정리

이번 주 글감은 메서드이다!

출처는 김영한의 자바 입문 - 코드로 시작하는 자바 첫걸음 (섹션 9. 메서드)이다.

메서드(Method)

자바에선 함수(function)를 메서드(method)라는 명칭으로 부른다.

  • 정확히는 메서드도 함수의 한 종류

(1) 메서드 선언(Method Declaration)

메서드 선언은 다음과 같다. 간단하게 훑어보자.

public static int add(int a, int b)
1. 접근 제어자(Modifier), public으로 호출할 경우 다른 클래스에서 호출 가능
2. static, 객체를 생성하지 않고 호출할 수 있는 정적 메서드
3. 반환 타입(Return Type), 메서드가 실행된 후 반환하는 데이터의 타입이며 int로 선언할 경우 반환되는 데이터의 타입은 int형이다.

  • 이때, 메서드가 값을 반환하지 않을 경우는 void를 사용
  1. 이름(Name), 메서드의 이름으로써 호출할 때 사용한다.
  • ex. add(1, 2)
  1. 매개변수(Parameter), 입력값으로 메서드 내부에서 사용할 수 있는 변수이며 선택 사항이다.
  • 즉, 매개변수는 반드시 필요한 값은 아니다. 필요하지 않을 경우, 다음과 같이 선언할 수 있다.
    - ex. add()
  1. 본문(Body), 실제 메서드의 코드가 중괄호 사이에 위치

(2) 메서드 반환 타입(Method Return)

반환 타입이 지정된 메서드의 경우, 반드시 return이 필요하다.

  • 가끔 코테 문제를 풀 때, return을 작성하지 않아 종종 마주했던 오류(Missing return statement.)가 이 개념에서 기인한 것

(3) 메서드 호출과 값 전달

자바는 항상 변수의 값을 복사해서 대입한다. 즉, Call by value임을 기억

그렇다면, 변수의 값을 복사해서 대입한다는 개념을 기반으로 다음 코드를 풀어보면?

public class MethodValue1 {
    public static void main(String[] args) {
        int num1 = 5;
        System.out.println("1. changeNumber 호출 전, num1: " + num1);
        changeNumber(num1);
        System.out.println("4. changeNumber 호출 후, num1: " + num1);
    }

    public static void changeNumber(int num2) {
        System.out.println("2. changeNumber 변경 전, num2: " + num2);
        num2 = num2 * 2;
        System.out.println("3. changeNumber 변경 후, num2: " + num2);
    }
}

답은 다음과 같다.
num1 = 5
num1 = 5
num1 = 10
num1 = 5

따라서 num2의 값을 바꾸더라도 num1에는 영향을 주지 않는다.

(4) 메서드와 형변환(Type casting)

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

다음 코드를 보자.

public class MethodCasting1 {
    public static void main(String[] args) {
        double number = 1.5;
        printNumber(number); // double을 int형에 대입하므로 컴파일 오류
    }

    public static void printNumber(int n) {
        System.out.println("숫자: " + n);
    }
}

변수의 데이터 타입은 double 타입이지만, 메서드의 매개변수가 int 타입인 경우 다음과 같은 오류가 발생한다.

java: incompatible types: possible lossy conversion from double to int

명시적 형변환

따라서 이럴 경우는 명시적 형변환을 사용한다.

printNumber((int) number);

자동 형변환

단, 반대의 경우(변수의 데이터 타입은 int, 메서드의 매개변수 타입은 double)는 자동 형변환이 이루어진다.

왜냐하면 int(4 byte)보다 큰 double(8 byte)에 int 값을 담는 것은 문제가 되지 않기 때문이다.

  • 즉, 비유를 하자면 큰 그릇에 작은 물건을 담는 경우는 문제가 없는 느낌?

(5) 메서드 오버로딩(Overloading)

자바는 메서드의 이름 뿐만 아니라 매개변수 정보를 함께 사용해서 메서드를 구분한다.

  • 즉, 이름이 같고 매개변수가 다른 메서드를 정의할 수 있다.

이름은 같고, 매개변수는 다르다.

이러한 메서드를 여러 개 정의하는 개념을 메서드 오버로딩이라고 한다.

  • 직역하자면 과하게 물건을 담았다라는 느낌?

오버로딩 규칙

이름이 같아도 매개변수의 타입 및 순서가 다르면 오버로딩은 할 수 있다. 하지만, 반환 타입이 다른 메서드는 오버로딩을 할 수 없다.

다음 예시를 보자.

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

차례대로 오버로딩 성공실패의 예시이다.

첫 번째 케이스는 이름은 동일하지만, 매개변수의 타입과 개수가 다르다.
하지만, 두 번째 케이스는 반환 타입은 다르지만 매개변수의 정보가 동일하므로 오버로딩이 될 수 없는 것이다.

메서드 시그니처(method signature)

메서드 시그니처 = 메서드 이름 + 매개변수 타입(순서)

메서드 시그니처 개념에서 엿볼 수 있듯이,

메서드 이름이 같아도, 메서드 시그니처가 다르면 다른 메서드로 간주한다. 그리고 이때, 반환 타입은 시그니처에 포함되지 않는다. 꼭 기억하자!

다양한 메서드 오버로딩

  1. 매개변수의 갯수가 다른 오버로딩
public static int add(int a, int b)
public static int add(int a, int b, int c)

2-1. 매개변수의 타입이 다른 오버로딩

public static void myMethod(int a, double b)
public static void myMethod(double a, int b)

2-2. 매개변수의 타입이 다른 오버로딩의 추가 케이스

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

문제와 풀이

사실상 이 포스팅의 핵심!

스스로 코드를 작성해보면서 정말 코딩 경험이 적다는걸 체감할 수 있는 부분들이 있었다.

한 번 살펴보자.

1. 평균값 리팩토링

  • 내가 작성한 코드
package method;

// TODO 문제 1. 평균값 리팩토링 : "메서드를 사용하도록 리팩토링 해보자."
// TODO 1. a,b,c가 사용할 메서드를 만들자.
public class Method {
    public static void main(String[] args) {
        int a = 1; // 입력으로 값을 할당과 동시에 메서드를 호출하도록 리팩토링
        int b = 2;
        int c = 3;

        double firstResult = average(a, b, c); // 메서드 값 반환과 동시에 값을 호출하도록 리팩토링
        System.out.println("평균값: " + firstResult);

        int x = 15;
        int y = 25;
        int z = 35;

        double secondResult = average(x, y, z);
        System.out.println("평균값: " + secondResult);
    }

    private static double average(int a, int b, int c) {
        int sum = a + b + c;

        return sum / 3.0;
    }
}

2. 특정 숫자만큼 같은 메시지를 반복 출력

키워드는 특정 숫자, 같은 메시지, 반복

  • 내가 작성한 코드
package method;

// TODO 1. 각 for문 내부에 sout가 있음을 확인
// TODO 2. 따라서 for문에 공통적으로 넘겨줄 String을 메서드를 단 한 번만 반복함으로써 수행
public class Method {
    public static void main(String[] args) {
        repeat("Hello, world!");
    }

    private static void repeat(String message) {
        for (int i = 0; i < 3; i ++) {
            System.out.println(message);
        }

        for (int i = 0; i < 5; i ++ ){
            System.out.println(message);
        }

        for (int i = 0; i < 7; i ++) {
            System.out.println(message);
        }
    }
}

3. 입출금 리팩토링

처음으로 내가 작성한 코드와 정답 코드가 일치해서 소소하게 기분이 좋았던 문제!

package method;

// TODO 1. 공통된 변수 balance를 main()에서 생성
// TODO 2. 입금 및 출금에 따른 메서드를 각각 생성
// TODO 3. 입금 및 출금에 따른 메서드 값을 공통 변수인 balance에 업데이트
// TODO 4. 각 로직에 따른 출력은 해당 메서드에서, 합 출력은 main()에서 진행
public class Method {
    public static void main(String[] args) {
        int balance = 10000;
        balance = deposit(1000, balance);
        balance = withdraw(2000, balance);

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

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

    private static int withdraw(int withdrawAmount, int balance) {
        if (balance >= withdrawAmount) {
            balance -= withdrawAmount;
            System.out.println(withdrawAmount + "원을 출금하였습니다. 현재 잔액: " + balance + "원");
        } else {
            System.out.println(withdrawAmount + "원을 출금하려 했으나 잔액이 부족합니다.");
        }

        return balance;
    }
}

4. 은행 계좌 입출금

이 문제는 내가 요구 사항을 제대로 파악하지 않고 풀어서 애매한 풀이를 작성했던 코드였다.

  • 하지만, 문제에서 명확하게 입력값이 어떻게 들어온다는 내용이 없었기 때문에 나처럼 착각할 수도 있지 않을까? 싶었던 문제였다.

인프런 게시판에 질문을 올려서 아래와 같은 답변을 받고, 잠깐의 반성을 한 후 수정해서 다시 푼 문제였다.

  • 내가 작성한 코드
package method;

// TODO 1. 공통 출력 부분을 start()라는 메서드로 따로 빼서 반복 호출하여 수행
// TODO 2. 입금 출금 등 요구 사항에 따라 구현이 다른 부분을 메서드로 추출
// TODO 3. 이때, 선택에 따른 숫자값과 입금액 및 출금액 등을 파라미터로 전달
// TODO 4. 잔액 확인 및 종료는 별다른 연산이 필요 없으므로 void로 설계
// TODO 5. 이때, 종료는 종료라는 책임이 해당 메서드에 배당이 되어야 하므로 메서드 내부에서 System.exit(0);을 수행

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Method {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str;

        int balance = 0;
        // TODO 1. "사용자의 입력이 계속 들어오는" + int choice = scanner.nextInt(); 및 switch(choice) { case 1: ..
        // TODO -> 한 줄에 하나씩 값이 들어옴
        // TODO 2. 어차피 "4"라는 값이 들어올 경우, 시스템은 반드시 종료됨 -> 따라서 별다른 조건 없이 무조건 while()을 실행하는 방향으로 구현
        while (true) {
            int choice = Integer.parseInt(br.readLine());
            int money;

            if (choice == 1) {
                money = Integer.parseInt(br.readLine());
                balance = deposit(choice, balance, money);
            } else if (choice == 2) {
                money = Integer.parseInt(br.readLine());
                balance = withDraw(choice, balance, money);
            } else if (choice == 3) {
                check(choice, balance);
            } else {
                exit(choice);
            }
        }
    }

    private static int depositOrWithdraw(StringTokenizer st) {
        return Integer.parseInt(st.nextToken());
    }

    private static void start() {
        System.out.println("---------------------------------");
        System.out.println("1.입금 | 2.출금 | 3.잔액 확인 | 4.종료");
        System.out.println("---------------------------------");
    }

    private static int deposit(int index, int balance, int depositAmount) {
        start();
        System.out.println("선택: " + index);

        balance += depositAmount;
        System.out.println("입금액을 입력하세요: " + balance);
        System.out.println(balance + "원을 입금하였습니다. 현재 잔액: " + balance + "원");

        return balance;
    }

    private static int withDraw(int index, int balance, int withdramAmount) {
        start();
        System.out.println("선택: " + index);

        if (balance >= withdramAmount) {
            balance -= withdramAmount;
            System.out.println("출금액을 입력하세요: " + withdramAmount);
            System.out.println(balance + "원을 출금하였습니다. 현재 잔액: " + balance + "원");
        } else {
            System.out.println(withdramAmount + "원을 출금하려 했으나 잔액이 부족합니다.");
        }

        return balance;
    }

    private static void check(int index, int balance) {
        start();
        System.out.println("선택: " + index);
        System.out.println("현재 잔액: " + balance + "원");
    }

    private static void exit(int index) {
        start();
        System.out.println("선택: " + index);
        System.out.println("시스템을 종료합니다.");
        System.exit(0);
    }
}

특히, 마지막 문제는 같은 스터디원 분이 다음과 같은 피드백을 주셔서 생각해볼 거리들이 좀 있었다.

1. 각 메서드별로 존재하는 System.out.println("선택: " + index);는 main() 내부의 if-else if-else 분기에 따라 실행하도록 두는 건?
2. start() 또한 main() - if 분기점 이전에 실행하도록 두는건?
3. 고런데 System.out.println("선택: " + index);이 필요할까?

이번 주 블로그 원정대 포스팅도 끝~

profile
B - B = 이은비

1개의 댓글

comment-user-thumbnail
2024년 2월 11일

캬캬 저는 항상 문제 슥 보고 바로 풀이 보는데 역시 성실의 아이콘 BB

답글 달기