Study/Java

[Java] 메소드(method)

the.Dev.Cat 2026. 4. 5. 04:27
Java 입문 — 메서드

김영한의 자바 입문 강의 내용을 정리한 글이다.

1. 메서드가 필요한 이유

같은 연산을 여러 번 반복해야 하는 상황을 생각해 보자.

// 계산1 int a = 1; int b = 2; System.out.println(a + "+" + b + " 연산 수행"); int sum1 = a + b; System.out.println("결과1 출력: " + sum1); // 계산2 int x = 10; int y = 20; System.out.println(x + "+" + y + " 연산 수행"); int sum2 = x + y; System.out.println("결과2 출력: " + sum2);

계산1과 계산2의 구조가 완전히 동일하다. 변수 이름과 값만 다를 뿐이다. 이 연산을 100곳에서 써야 한다면 100번 반복해야 하고, 출력 형식을 바꾸려면 100곳을 모두 수정해야 한다.

수학에서 add(a, b) = a + b라는 함수를 정의해두면 add(1, 2), add(5, 6)처럼 여러 번 호출해 사용할 수 있다. 자바는 이 개념을 메서드(Method)라는 이름으로 언어에 통합했다.

a + b add 메서드 입력 1, 2 출력 3 add(1, 2) → 3 / add(5, 6) → 11 / add(3, 5) → 8

2. 메서드 구조

메서드는 메서드 선언(Method Declaration)메서드 본문(Method Body)으로 구성된다.

제어자 반환타입 메서드이름(매개변수 목록) { 메서드 본문 }
public static int add(int a, int b) { int sum = a + b; return sum; }
public static 제어자 int 반환 타입 add 메서드 이름 (int a, int b) 매개변수 { ... } 메서드 본문
구성 요소예시의미
제어자public static접근 범위와 호출 방식을 지정한다. 지금은 항상 이 두 키워드를 함께 쓴다
반환 타입int메서드가 실행 후 돌려주는 값의 타입
메서드 이름add이 이름으로 메서드를 호출한다
매개변수int a, int b호출할 때 전달하는 입력값. 메서드 안에서만 유효한 지역 변수다
메서드 본문{ ... }실제 실행될 코드 블록. 호출하는 쪽은 내용을 몰라도 된다

public은 다른 클래스에서도 호출할 수 있다는 뜻이고, static은 객체를 생성하지 않고 호출할 수 있는 정적 메서드라는 뜻이다. 두 키워드의 상세한 내용은 이후 클래스·객체 편에서 다룬다.


3. 매개변수와 인수

// 메서드 정의 — str, age가 매개변수(Parameter) int call(String str, int age) { ... } // 메서드 호출 — "hello", 20이 인수(Argument) call("hello", 20);
  • 매개변수(Parameter): 메서드를 정의할 때 선언하는 변수. "중간에서 전달하는 변수"라는 의미다.
  • 인수(Argument): 메서드를 호출할 때 넘기는 실제 값. "들어가는 수"라는 의미다.

호출할 때 인수의 타입, 순서, 개수가 매개변수와 일치해야 한다. 실무에서는 두 용어를 혼용하는 경우가 많다.


4. 메서드 호출 흐름

add(5, 10) 호출이 어떻게 처리되는지 단계별로 따라가 보자.

// 1: add 메서드를 5, 10을 전달하며 호출 int sum1 = add(5, 10); // 2: 매개변수 a=5, b=10 대입 후 메서드 실행 public static int add(int a=5, int b=10) { int sum = a + b; // 3: sum = 15 return sum; // 4: 15 반환 } // 5: 반환값 15가 호출 위치를 대체, sum1에 저장 int sum1 = 15;
main() int sum1 = ? add(5, 10) 호출 add() 스택 프레임 int a = 5, b = 10 int sum = 15 return 15 int sum1 = 15 반환값 저장

메서드 호출이 끝나면 매개변수 a, b와 지역 변수 sum은 모두 소멸된다. 스택 프레임이 제거되기 때문이다.


5. void와 return

매개변수도, 반환 타입도 없는 메서드가 있다.

public static void printHeader() { System.out.println("= 프로그램을 시작합니다 ="); return; // void의 경우 생략 가능 } public static void printFooter() { System.out.println("= 프로그램을 종료합니다 ="); }
  • 매개변수가 없는 경우: 선언에서 괄호 안을 비운다 — printHeader(). 호출할 때도 인수 없이 printHeader();로 호출한다.
  • 반환 타입이 없는 경우: void를 반환 타입 자리에 쓴다. void 메서드에서 return은 메서드를 즉시 종료하는 역할을 하며, 마지막 줄에서는 생략할 수 있다.

void 메서드의 반환값을 변수에 받으려 하면 컴파일 오류가 발생한다. 반환 타입이 void이므로 반환할 값 자체가 없다.


6. 반환 타입과 return의 규칙

모든 경로에서 return이 있어야 한다

반환 타입이 있는 메서드는 실행 가능한 모든 경로에서 return이 보장되어야 한다.

// 컴파일 오류 — else 분기에서 return이 없다 public static boolean odd(int i) { if (i % 2 == 1) { return true; } // i가 짝수면 return에 도달하지 못함 → java: missing return statement } // 올바른 버전 public static boolean odd(int i) { if (i % 2 == 1) { return true; } else { return false; } }

return을 만나면 즉시 메서드를 빠져나간다

void 메서드에서 return을 조기 탈출에 활용할 수 있다. 이후 코드를 실행하지 않고 메서드를 종료한다.

public static void checkAge(int age) { if (age < 18) { System.out.println(age + "살, 미성년자는 출입이 불가능합니다."); return; // 여기서 메서드 종료, 아래 코드는 실행되지 않는다 } System.out.println(age + "살, 입장하세요."); }

7. 자바는 항상 값을 복사해서 전달한다

자바에서 가장 중요한 원칙 중 하나다.

자바는 항상 변수의 값을 복사해서 대입한다.

변수 복사

int num1 = 5; int num2 = num1; // num1의 값 5를 읽어 복사 → num2에 5 저장 num2 = 10; // num2만 10으로 변경 System.out.println("num1=" + num1); // num1=5 System.out.println("num2=" + num2); // num2=10

num2 = num1num1 자체가 num2로 들어가는 것이 아니다. num1에 있는 값 5를 읽어 num2에 복사하는 것이다. 이후 두 변수는 독립적이다.

5 num1 변경 없음 값 복사 5 num2 복사 직후 num2=10 10 num2 변경 후

메서드 호출과 값 복사

public static void main(String[] args) { int num1 = 5; changeNumber(num1); // num1의 값 5를 복사해서 전달 System.out.println("num1: " + num1); // num1: 5 (변경되지 않음) } public static void changeNumber(int num2) { num2 = num2 * 2; // num2만 10으로 변경, num1과 무관 System.out.println("num2: " + num2); // num2: 10 }

changeNumber(num1) 호출 시 num1의 값 5num2에 복사된다. num2를 아무리 바꿔도 num1에는 영향이 없다. 두 변수는 각자의 스택 프레임에 독립적으로 존재하기 때문이다.

main() 스택 프레임 num1 = 5 값 5 복사 changeNumber() 프레임 num2 = 5 (복사본) num2 = 10 (변경) num1 = 5 유지 (영향 없음) 두 프레임은 완전히 독립적이다

반환값으로 변경 결과를 받는 방법

메서드가 변경한 값을 호출자에게 반영하려면 return으로 돌려받아야 한다.

public static void main(String[] args) { int num1 = 5; num1 = changeNumber(num1); // 반환값을 num1에 다시 저장 System.out.println("num1: " + num1); // num1: 10 } public static int changeNumber(int num2) { num2 = num2 * 2; return num2; // 변경된 값을 반환 }

참조형(객체, 배열) 변수도 같은 원칙이 적용된다. 참조형 변수의 값은 객체의 주소이므로, 그 주소가 복사되어 전달된다. 결과적으로 같은 객체를 가리키게 되어 내부 상태가 바뀐다. 이것은 객체 편에서 다룬다.


8. JVM 스택과 스택 프레임

메서드가 호출될 때 JVM 내부에서 어떤 일이 벌어지는지 알면 값 복사 원칙이 왜 그런지 명확히 이해된다.

영역용도
스택(Stack)메서드 호출 정보와 지역 변수. 스레드마다 독립적으로 존재한다
힙(Heap)객체, 배열 등 참조 타입. 모든 스레드가 공유한다
메서드 영역클래스 정보, static 변수, 상수. 프로그램 전체가 공유한다

메서드가 호출되면 JVM은 스택에 스택 프레임(Stack Frame)을 하나 쌓는다. 프레임 안에는 지역 변수 배열, 피연산자 스택, 런타임 상수 풀 참조가 들어간다. 메서드 실행이 끝나면 프레임은 스택에서 즉시 제거된다.

JVM 스택 (LIFO) main() 스택 프레임 int sum1 (반환값 대기) ... (다른 지역 변수) add() 스택 프레임 ← 현재 실행 int a = 5 int b = 10 int sum = 15 (계산 완료) 힙(Heap) 객체, 배열 등 참조 타입만 저장 add() 종료 시 프레임 제거 → a, b, sum 소멸

각 스택 프레임은 독립적이다. mainnum1changeNumbernum2가 이름이 같아도 서로 다른 프레임에 위치하므로 완전히 별개의 변수다. 이것이 값 복사 원칙의 물리적 근거다.


9. 메서드와 형변환

명시적 형변환

인수의 타입이 매개변수 타입보다 크면 데이터 손실이 발생할 수 있으므로 컴파일 오류가 발생한다. 강제로 변환해야 한다.

double number = 1.5; // printNumber(number); // 컴파일 오류: incompatible types: possible lossy conversion printNumber((int) number); // 명시적 형변환: 1.5 → 1 (소수점 버림) public static void printNumber(int n) { System.out.println("숫자: " + n); // 숫자: 1 }

자동 형변환

인수의 타입이 매개변수 타입보다 작으면 자동으로 변환된다. int < long < double 순으로 더 큰 타입으로 자동 확장된다.

int number = 100; printNumber(number); // int → double 자동 형변환 public static void printNumber(double n) { System.out.println("숫자: " + n); // 숫자: 100.0 }

메서드를 호출할 때 인수를 매개변수에 대입하는 것도 결국 변수 대입이므로, 일반 변수 대입의 형변환 규칙이 그대로 적용된다.


10. 메서드 오버로딩

같은 기능을 수행하지만 매개변수의 개수나 타입이 다른 메서드를 여러 개 정의할 수 있다. 이것을 메서드 오버로딩(Overloading)이라 한다.

public static int add(int a, int b) { return a + b; } // 매개변수 개수가 다르다 — 오버로딩 성공 public static int add(int a, int b, int c) { return a + b + c; } // 매개변수 타입이 다르다 — 오버로딩 성공 public static double add(double a, double b) { return a + b; }
System.out.println(add(1, 2)); // add(int, int) 호출 → 3 System.out.println(add(1, 2, 3)); // add(int, int, int) 호출 → 6 System.out.println(add(1.2, 1.5)); // add(double, double) 호출 → 2.7

메서드 시그니처

자바가 메서드를 구분하는 기준이 메서드 시그니처(Method Signature)다.

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

반환 타입은 시그니처에 포함되지 않는다. 따라서 이름과 매개변수 타입이 동일하고 반환 타입만 다른 메서드는 오버로딩이 되지 않는다.

// 오버로딩 실패 — 시그니처가 동일하다 int add(int a, int b) // 시그니처: add(int, int) double add(int a, int b) // 시그니처: add(int, int) → 동일! 컴파일 오류
오버로딩 조건가능 여부
매개변수 개수가 다르다가능
매개변수 타입이 다르다가능
매개변수 순서가 다르다가능
반환 타입만 다르다불가 (컴파일 오류)

오버로딩과 자동 형변환

인수와 정확히 일치하는 메서드가 없으면 자동 형변환이 가능한 메서드를 찾아 호출한다. 컴파일러는 정확히 일치하는 시그니처를 우선 찾고, 없으면 자동 형변환이 가능한 시그니처를 찾는다.

// add(int, int)가 없고 add(double, double)만 있는 경우 add(1, 2); // int → double 자동 형변환, add(double, double) 호출 → 3.0

실무에서의 오버로딩

자바 표준 라이브러리 System.out.println()이 대표적인 예다. 다양한 타입을 인수로 받을 수 있는 이유가 타입별 오버로딩 때문이다.

System.out.println(42); // println(int) System.out.println(3.14); // println(double) System.out.println("hello"); // println(String) System.out.println(true); // println(boolean)

11. 메서드 명명 관례

변수 이름은 명사로 짓지만, 메서드 이름은 동사로 시작하는 것이 관례다. 메서드는 무언가를 수행하기 때문이다.

구분관례예시
변수명사, camelCasecustomerName, totalSum, isAvailable
메서드동사+명사, camelCaseprintReport(), calculateSum(), addCustomer()

메서드 이름은 그 메서드가 하는 일을 명확히 전달해야 한다. method1(), doSomething() 같은 이름은 의도를 숨긴다.


12. Extract Method 리팩토링

메서드는 단순히 코드 재사용만을 위한 것이 아니다. 긴 코드를 의미 있는 단위로 쪼개는 Extract Method 패턴도 메서드의 중요한 역할이다.

리팩토링 전: main()이 입금·출금의 세부 구현까지 모두 담고 있다.

public static void main(String[] args) { int balance = 10000; int depositAmount = 1000; balance += depositAmount; System.out.println(depositAmount + "원을 입금하였습니다. 현재 잔액: " + balance + "원"); int withdrawAmount = 2000; if (balance >= withdrawAmount) { balance -= withdrawAmount; System.out.println(withdrawAmount + "원을 출금하였습니다. 현재 잔액: " + balance + "원"); } else { System.out.println(withdrawAmount + "원을 출금하려 했으나 잔액이 부족합니다."); } System.out.println("최종 잔액: " + balance + "원"); }

리팩토링 후: main()은 전체 흐름만 보여주는 목차가 됐다.

public static void main(String[] args) { int balance = 10000; balance = deposit(balance, 1000); balance = withdraw(balance, 2000); System.out.println("최종 잔액: " + balance + "원"); } 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; }

입금·출금 로직이 각자의 메서드로 분리되어, 출금 조건이 바뀌면 withdraw()만 수정하면 된다. 이런 리팩토링을 메서드 추출(Extract Method)이라 한다. 코드 재사용이 목적이 아니어도 충분히 가치 있다. 메서드의 이름이 코드를 읽기 좋게 만드는 문서 역할을 한다.


13. 메서드 사용의 장점

장점설명
코드 재사용같은 기능을 여러 곳에서 호출만 하면 된다
가독성의미 있는 이름을 붙인 메서드는 코드를 읽는 데 도움을 준다
모듈성큰 프로그램을 작고 관리 가능한 단위로 분리할 수 있다
유지보수변경이 필요할 때 해당 메서드 하나만 수정하면 된다
추상화호출하는 쪽은 구현 방식을 몰라도 된다
테스트 용이성메서드 단위로 독립적인 테스트가 가능하다

메서드를 잘 설계하는 기준으로 단일 책임 원칙(Single Responsibility Principle)이 있다. 하나의 메서드는 하나의 일만 해야 한다는 원칙이다. 메서드에 "그리고"가 붙는 설명이 필요하다면 분리를 고려할 때다.