프로그램이 길어지면 같은 계산이나 출력 코드가 여러 곳에서 반복됩니다. 메서드는 하나의 작업을 이름으로 묶어 필요할 때 호출하는 기능입니다.

이번 글에서는 메서드를 만드는 방법부터 매개변수와 인수, 반환값, 값 전달, 오버로딩까지 차례대로 정리합니다. 변수의 스코프와 형변환이 아직 헷갈린다면 자바 스코프와 형변환 정리를 먼저 확인하는 편이 좋습니다.


메서드가 필요한 이유

주문 금액에 배송비를 더하는 코드를 여러 번 작성한다고 생각해보겠습니다.

int firstOrder = 18000;
int firstTotal = firstOrder + 3000;

int secondOrder = 27000;
int secondTotal = secondOrder + 3000;

System.out.println(firstTotal);
System.out.println(secondTotal);

계산은 단순하지만 같은 규칙이 반복됩니다. 배송비 정책이 바뀌면 여러 위치를 모두 찾아 수정해야 합니다.

반복되는 계산을 메서드로 분리하면 규칙을 한곳에서 관리할 수 있습니다.

public static int calculateTotal(int orderPrice) {
    return orderPrice + 3000;
}

필요한 곳에서는 메서드 이름으로 호출합니다.

int firstTotal = calculateTotal(18000);
int secondTotal = calculateTotal(27000);

메서드 이름만 보아도 주문의 최종 금액을 계산한다는 의도를 알 수 있습니다.


메서드의 기본 구조

메서드는 크게 선언부본문으로 나뉩니다.

public static int calculateTotal(int orderPrice) {
    int deliveryFee = 3000;
    return orderPrice + deliveryFee;
}

각 부분의 역할은 다음과 같습니다.

부분코드역할
접근·정적 제어자public static현재 단계에서 메서드를 호출할 수 있게 지정
반환 타입int실행 결과로 돌려줄 값의 타입
메서드 이름calculateTotal작업을 나타내는 이름
매개변수int orderPrice호출할 때 전달받을 값
메서드 본문{ ... }실제로 실행할 코드

입문 단계에서는 main에서 직접 호출하기 쉽도록 public static 메서드를 사용합니다. 객체와 접근 제어자를 배우면 이 부분의 의미를 더 자세히 이해할 수 있습니다.

메서드 호출 흐름

다음 코드를 실행하면 calculateTotal 메서드로 이동합니다.

int total = calculateTotal(18000);

호출 과정을 순서대로 보면 다음과 같습니다.

calculateTotal(18000) 호출
        ↓
orderPrice에 18000 전달
        ↓
18000 + 3000 계산
        ↓
21000 반환
        ↓
total에 21000 저장

메서드 실행이 끝나면 호출한 위치로 돌아와 다음 코드를 이어서 실행합니다.


매개변수와 인수

메서드에 값을 전달할 때 매개변수인수라는 용어를 사용합니다.

public static int calculateTotal(int orderPrice) {
    return orderPrice + 3000;
}

int total = calculateTotal(18000);
  • int orderPrice는 메서드가 값을 받을 공간인 매개변수입니다.
  • 18000은 메서드를 호출할 때 실제로 전달한 인수입니다.

매개변수가 여러 개라면 호출할 때 개수, 순서, 타입을 맞춰야 합니다.

public static int calculateTotal(int orderPrice, int deliveryFee) {
    return orderPrice + deliveryFee;
}

int total = calculateTotal(18000, 3000);

첫 번째 인수 18000orderPrice에 들어갑니다. 두 번째 인수 3000deliveryFee에 들어갑니다.


반환값과 return

메서드가 계산 결과를 호출한 곳에 돌려주려면 return을 사용합니다.

public static int calculateDiscount(int price, int discountRate) {
    return price * discountRate / 100;
}

반환 타입이 int이므로 반드시 정수 값을 반환해야 합니다.

int discount = calculateDiscount(50000, 10);
System.out.println(discount); // 5000

반환된 값은 변수에 저장하거나 다른 계산에 바로 사용할 수 있습니다.

int finalPrice = 50000 - calculateDiscount(50000, 10);
System.out.println(finalPrice); // 45000

모든 실행 경로에서 값을 반환해야 합니다

반환 타입이 있는 메서드는 어떤 조건에서도 값을 반환해야 합니다.

public static String getDeliveryType(int orderPrice) {
    if (orderPrice >= 30000) {
        return "무료 배송";
    }

    return "일반 배송";
}

마지막 return이 없다면 주문 금액이 30000원보다 작을 때 돌려줄 값이 없습니다. 이 경우 컴파일 오류가 발생합니다.

return을 만나면 메서드가 끝납니다

return은 값을 돌려주는 역할과 함께 현재 메서드를 즉시 종료합니다.

public static int calculateDeliveryFee(int orderPrice) {
    if (orderPrice >= 30000) {
        return 0;
    }

    return 3000;
}

주문 금액이 30000원 이상이면 0을 반환하고 메서드가 끝납니다. 아래의 return 3000은 실행되지 않습니다.


값을 반환하지 않는 void 메서드

결과값을 돌려줄 필요 없이 출력이나 알림만 수행하는 메서드는 반환 타입으로 void를 사용합니다.

public static void printOrderMessage(String productName) {
    System.out.println(productName + " 주문이 접수되었습니다.");
}

호출할 때 반환값을 변수에 저장하지 않습니다.

printOrderMessage("키보드");

실행 결과입니다.

키보드 주문이 접수되었습니다.

void 메서드에서도 return;을 사용해 실행을 일찍 끝낼 수 있습니다.

public static void printOrderMessage(String productName) {
    if (productName.isBlank()) {
        System.out.println("상품 이름이 비어 있습니다.");
        return;
    }

    System.out.println(productName + " 주문이 접수되었습니다.");
}

잘못된 입력을 먼저 처리하고 종료하면 정상 흐름의 들여쓰기가 깊어지지 않습니다.


자바는 값을 복사해서 전달합니다

메서드를 호출할 때 변수 자체가 이동하는 것은 아닙니다. 변수에 들어 있는 값이 복사되어 매개변수에 전달됩니다.

public class MethodValueExample {
    public static void main(String[] args) {
        int price = 20000;

        changePrice(price);

        System.out.println(price); // 20000
    }

    public static void changePrice(int copiedPrice) {
        copiedPrice = 15000;
    }
}

price의 값 20000copiedPrice에 복사됩니다. 두 변수는 서로 다른 공간이므로 메서드 안에서 copiedPrice를 바꿔도 price는 변하지 않습니다.

호출한 변수의 값을 바꾸고 싶다면 결과를 반환받아 다시 저장해야 합니다.

public class MethodValueExample {
    public static void main(String[] args) {
        int price = 20000;

        price = applyCoupon(price);

        System.out.println(price); // 15000
    }

    public static int applyCoupon(int price) {
        return price - 5000;
    }
}

applyCoupon이 반환한 값을 price에 다시 대입했기 때문에 값이 변경됩니다.


메서드 호출과 형변환

인수 타입과 매개변수 타입이 다르면 형변환 가능 여부를 확인해야 합니다.

작은 범위에서 큰 범위로 전달할 때는 자동 형변환이 가능합니다.

public static void printWeight(double weight) {
    System.out.println("무게: " + weight + "kg");
}

int packageWeight = 3;
printWeight(packageWeight);

int3double3.0으로 자동 변환됩니다.

반대로 doubleint 매개변수에 전달하면 소수점 정보가 사라질 수 있습니다. 이때는 명시적 형변환이 필요합니다.

public static void printCount(int count) {
    System.out.println("수량: " + count);
}

double measuredCount = 3.8;
printCount((int) measuredCount); // 3

형변환으로 값이 잘릴 수 있으므로 의도한 동작인지 먼저 확인해야 합니다.


메서드 오버로딩

매개변수의 개수나 타입이 다르면 같은 이름의 메서드를 여러 개 만들 수 있습니다. 이를 메서드 오버로딩이라고 합니다.

public static int calculateDeliveryFee(int orderPrice) {
    return orderPrice >= 30000 ? 0 : 3000;
}

public static int calculateDeliveryFee(int orderPrice, boolean remoteArea) {
    int fee = calculateDeliveryFee(orderPrice);

    if (remoteArea) {
        fee += 5000;
    }

    return fee;
}

호출할 때 전달한 인수에 따라 알맞은 메서드가 선택됩니다.

int basicFee = calculateDeliveryFee(20000);
int remoteFee = calculateDeliveryFee(20000, true);

오버로딩을 구분하는 기준은 메서드 이름과 매개변수의 타입·순서입니다. 반환 타입만 다르게 만드는 것은 오버로딩이 아닙니다.

public static int getFee(int price) {
    return 3000;
}

// 반환 타입만 다르므로 함께 선언할 수 없습니다.
// public static double getFee(int price) {
//     return 3000.0;
// }

같은 작업을 입력 형태에 따라 조금씩 다르게 처리할 때 오버로딩이 유용합니다. 하지만 경우가 너무 많아지면 어떤 메서드가 호출되는지 파악하기 어려워질 수 있습니다.


전체 예시

지금까지 배운 내용을 사용해 최종 주문 금액을 계산해보겠습니다.

public class OrderCalculator {
    public static void main(String[] args) {
        int orderPrice = 45000;
        int discountRate = 10;
        boolean remoteArea = true;

        int discount = calculateDiscount(orderPrice, discountRate);
        int deliveryFee = calculateDeliveryFee(orderPrice, remoteArea);
        int finalPrice = orderPrice - discount + deliveryFee;

        printReceipt(orderPrice, discount, deliveryFee, finalPrice);
    }

    public static int calculateDiscount(int price, int rate) {
        return price * rate / 100;
    }

    public static int calculateDeliveryFee(int orderPrice) {
        return orderPrice >= 30000 ? 0 : 3000;
    }

    public static int calculateDeliveryFee(
        int orderPrice,
        boolean remoteArea
    ) {
        int fee = calculateDeliveryFee(orderPrice);

        if (remoteArea) {
            fee += 5000;
        }

        return fee;
    }

    public static void printReceipt(
        int orderPrice,
        int discount,
        int deliveryFee,
        int finalPrice
    ) {
        System.out.println("주문 금액: " + orderPrice + "원");
        System.out.println("할인 금액: " + discount + "원");
        System.out.println("배송비: " + deliveryFee + "원");
        System.out.println("최종 금액: " + finalPrice + "원");
    }
}

실행 결과입니다.

주문 금액: 45000원
할인 금액: 4500원
배송비: 5000원
최종 금액: 45500원

main은 전체 실행 순서만 보여줍니다. 할인, 배송비, 출력 규칙은 각각의 메서드가 담당합니다.


메서드 이름 짓기

변수 이름은 주로 값을 나타내는 명사를 사용합니다. 메서드 이름은 작업을 나타내므로 동사로 시작하는 편이 자연스럽습니다.

int finalPrice;
boolean freeDelivery;

calculateDiscount();
calculateDeliveryFee();
printReceipt();

method1, process, doSomething처럼 의미가 넓은 이름은 작업을 파악하기 어렵습니다. 메서드가 무엇을 계산하고 출력하는지 드러나는 이름을 사용하는 것이 좋습니다.


실수하기 쉬운 부분

반환 타입과 return 값의 타입이 맞아야 합니다

public static int getPrice() {
    return 15000;
}

반환 타입이 int라면 return 뒤에도 정수 값이 와야 합니다.

매개변수 순서를 확인해야 합니다

타입이 같은 매개변수가 여러 개라면 순서를 바꾸어도 컴파일 오류가 나지 않을 수 있습니다.

calculateDiscount(price, rate);

이름과 호출 순서를 함께 확인해야 잘못된 계산을 막을 수 있습니다.

메서드는 다른 메서드 안에 선언할 수 없습니다

메서드는 클래스 안에 선언합니다. main 메서드의 중괄호 안에 새로운 메서드를 선언하면 컴파일 오류가 발생합니다.

매개변수를 바꿔도 호출한 기본형 변수는 바뀌지 않습니다

자바는 호출할 때 값을 복사합니다. 변경 결과가 필요하다면 반환값을 받아 다시 저장해야 합니다.


정리

메서드는 관련된 코드를 하나의 작업으로 묶고 이름을 붙입니다. 반복을 줄이는 것뿐 아니라 코드의 역할을 명확하게 나누는 데도 도움이 됩니다.

  • 매개변수는 메서드가 전달받을 값을 선언합니다.
  • 인수는 호출할 때 실제로 전달하는 값입니다.
  • return은 결과를 돌려주고 메서드를 종료합니다.
  • void는 반환값이 없는 메서드에 사용합니다.
  • 자바는 메서드 호출 시 값을 복사해서 전달합니다.
  • 매개변수의 개수나 타입이 다르면 메서드를 오버로딩할 수 있습니다.

메서드를 만들 때는 먼저 작업을 한 문장으로 설명해보는 것이 좋습니다. 한 문장으로 이름 붙이기 어려울 정도로 역할이 많다면 메서드를 더 작게 나눌 수 있는지 살펴봐야 합니다.