학습 자료

메서드 — 코드를 이름으로 묶는다


Article

두 변수의 값을 맞바꾸는 코드를 짰다. 자주 쓸 것 같아 따로 떼어 이름을 붙였다.

public class Main {
    static void swap(int x, int y) {
        int t = x;
        x = y;
        y = t;
    }

    public static void main(String[] args) {
        int a = 1, b = 2;
        swap(a, b);
        System.out.println("a=" + a + " b=" + b);
    }
}
콘솔
  • a=1 b=2

아무것도 바뀌지 않았다. swap 안에서는 분명히 바꿨는데.

에러도 없다. 자바는 시킨 대로 했다. 왜 이게 시킨 대로인지가 이번 강의의 핵심이고, 답은 이미 4강에 있다.

코드에 이름을 붙인다

먼저 문법부터. 메서드는 이렇게 생겼다.

static int add(int a, int b) {
    return a + b;
}
static int add(int a, int b)
  1. static13강에서 답한다
  2. int돌려줄 값의 타입
  3. add이름
  4. (int a, int b)받을 값들 — 매개변수

return 은 값을 돌려주고 그 자리에서 메서드를 끝낸다. return 아래의 코드는 실행되지 않는다.

부를 때는 이름과 괄호를 쓴다.

int sum = add(3, 4);   // sum 은 7

넘긴 34인자, 받는 쪽의 ab매개변수라고 부른다. 구분해서 알아두면 에러 메시지를 읽기 편하다.

void — 2강에서 미뤄둔 단어

돌려줄 값이 없는 메서드도 있다. 화면에 찍기만 하고 끝나는 경우다.

그럴 때 반환 타입 자리에 void 를 적는다. "비어 있다"는 뜻이다.

static void greet(String name) {
    System.out.println("안녕, " + name);
}

2강에서 public static void main 의 다섯 단어 중 하나로 만났던 그 void 다. main 은 일을 다 하고 나서 아무것도 돌려주지 않는다. JVM 은 값을 받을 생각이 없다.

void 메서드에서도 return; 을 쓸 수 있다. 값 없이 세미콜론만 붙이면 "여기서 그만"이라는 뜻이다.

static void greet(String name) {
    if (name == null) return;          // 여기서 끝낸다
    System.out.println("안녕, " + name);
}

7강에서 배운 조건문과 짝을 이루는 흔한 패턴이다. 잘못된 입력을 앞에서 걸러내고 나머지를 편하게 쓴다.

콜 스택 — 부른 곳으로 돌아가는 법

maingreet 를 부르고, greetprintln 을 부른다. 그런데 println 이 끝나면 어디로 돌아가야 할까?

JVM 은 메서드를 부를 때마다 접시를 쌓듯이 기록을 쌓는다. 이것을 콜 스택이라고 한다.

greet 안에서 println 을 실행하는 순간
  • println()
  • greet()
  • main()

Call Stack

맨 위가 지금 실행 중인 메서드다. 끝나면 접시를 치우고, 바로 아래 접시가 이어서 돈다.

println 이 끝난 뒤
  • greet()
  • main()

Call Stack

이 구조 덕분에 "돌아갈 곳"을 기억할 수 있다. 그리고 9강에서 본 예외 메시지의 at Main.main(Main.java:4) 가 바로 이 스택을 찍어낸 것이다. 18강에서 다시 만난다.

접시를 너무 많이 쌓으면 어떻게 될까. 자기 자신을 계속 부르게 해보자.

static void dig() {
    dig();
}
터미널
  • Exception in thread "main" java.lang.StackOverflowError

쌓을 자리가 바닥났다. 이 컴퓨터에서는 22,000번쯤 쌓이고 터졌다. 스택은 무한하지 않다.

그래서 swap 은 왜 안 됐나

이제 맨 처음 문제로 돌아간다.

자바는 값을 넘길 때 언제나 복사한다. 예외가 없다.

swap(a, b) 를 부른 순간

main 의 a·b 와 swap 의 x·y 는 서로 다른 상자다

a (main)
1
b (main)
2
x (swap)
1
← a 의 값을 복사
y (swap)
2
← b 의 값을 복사
swap 은 복사본 x·y 만 열심히 바꾸고 끝난다. a·b 는 구경만 했다.

swap 이 끝나면 xy 는 접시와 함께 치워진다. ab 는 처음부터 아무 일도 없었다.

자바에서 메서드는 변수 자체를 넘겨받을 수 없다. 그래서 값 두 개를 진짜로 맞바꾸고 싶다면 다른 방법을 찾아야 한다. 배열에 담아 넘기거나, 바뀐 값을 return 으로 돌려받는다.

그런데 배열은 바뀐다

같은 논리로 배열을 넘겨보자.

static void fill(int[] arr) {
    arr[0] = 99;
}

public static void main(String[] args) {
    int[] a = { 1, 2, 3 };
    fill(a);
    System.out.println(a[0]);
}
콘솔
  • 99

이번엔 바뀌었다. 방금 "언제나 복사한다"고 해놓고 말이 다른 것 같다.

말은 그대로다. 복사된 것은 주소다.

fill(a) 를 부른 순간

주소가 복사됐다. 가리키는 곳은 하나다

a (main)
0x1A4F
→ [ 1, 2, 3 ]
arr (fill)
0x1A4F
→ 같은 곳
arr[0] = 99 는 주소를 따라가서 저쪽의 첫 칸을 바꾼다. a 로 봐도 99 다.

4강에서 int[] b = a; 로 복사했을 때와 똑같다. 메서드 호출도 결국 대입이다.

그럼 fill 안에서 배열을 통째로 갈아치우면 어떻게 될까.

static void reassign(int[] arr) {
    arr = new int[]{ 7, 7, 7 };
}
콘솔
  • 1

안 바뀐다. arr 이라는 복사된 상자에 새 주소를 덮어썼을 뿐이다. maina 는 여전히 옛 주소를 들고 있다.

오버로딩 — 같은 이름, 다른 재료

이름이 같아도 매개변수가 다르면 여러 개를 둘 수 있다.

static int    add(int a, int b)          { return a + b; }
static double add(double a, double b)    { return a + b; }
static int    add(int a, int b, int c)   { return a + b + c; }
System.out.println(add(1, 2));        // 3
System.out.println(add(1.5, 2.5));    // 4.0
System.out.println(add(1, 2, 3));     // 6

컴파일러가 인자의 타입과 개수를 보고 어느 것을 부를지 고른다. 실행하기 전에 이미 정해진다.

printlnintString 이든 배열이든 다 받아주는 것도 오버로딩 덕분이다. 우리가 모르는 사이 수십 개가 준비되어 있었다.

반환 타입만 다른 것은 오버로딩이 아니다. int add(...)double add(...) 를 매개변수까지 똑같이 두면 컴파일 에러다. 부르는 쪽만 봐서는 무엇을 원하는지 알 수 없기 때문이다.

왜 전부 static 을 붙였나

이번 강의의 메서드에는 전부 static 이 붙어 있다. 떼어보면 이렇게 된다.

public class Main {
    void hi() { }

    public static void main(String[] args) {
        hi();
    }
}
터미널
  • error: non-static method hi() cannot be referenced from a static context

static 이 아닌 메서드는 static 인 곳에서 부를 수 없다고 한다. mainstatic 이라서 생긴 일이다.

2강에서 "static 은 객체를 만들지 않고도 부를 수 있다는 뜻"이라고만 하고 넘어갔다. 지금은 main 과 짝을 맞추려고 붙였다고만 알아두자.

이 에러는 11강에서 객체를 배우고 13강에서 static 을 배우면 저절로 풀린다. 자바를 시작한 사람이 첫 주에 가장 많이 만나는 에러이기도 하다.

정리하면

메서드는 코드에 이름을 붙인 것이고, 자바는 넘기는 값을 언제나 복사한다.

다시 짚어보기
  1. 01

    반환 타입 · 이름 · 매개변수

    돌려줄 게 없으면 void. 2강의 그 void 다.

    static int add(int a, int b) {
        return a + b;
    }
  2. 02

    return 은 값을 주고 즉시 끝낸다

    void 메서드에서도 return; 으로 빠져나올 수 있다.

  3. 03

    메서드를 부르면 콜 스택에 쌓인다

    끝나면 치워지고 부른 곳으로 돌아간다. 너무 쌓으면 StackOverflowError.

  4. 04

    인자는 언제나 복사된다

    기본형은 값이, 참조형은 주소가 복사된다. 그래서 swap 은 안 되고 fill 은 된다.

    swap(a, b);       // a, b 안 바뀐다
    fill(arr);        // arr 의 내용은 바뀐다
    reassign(arr);    // arr 은 안 바뀐다
  5. 05

    이름이 같아도 매개변수가 다르면 된다

    오버로딩. 반환 타입만 다른 것은 안 된다.

Part 3 이 끝났다. 조건으로 길을 나누고, 반복으로 같은 길을 여러 번 걷고, 배열로 값을 묶고, 메서드로 코드에 이름을 붙였다.

여기까지가 대부분의 언어에 다 있는 것들이다. 문법만 조금씩 다르다.

다음 Part 부터가 자바다. 지금까지 우리는 값과 코드를 따로 다뤘다. 값과 그 값을 다루는 코드를 한 덩어리로 묶으면 무슨 일이 벌어지는지 본다.

그 덩어리를 객체라고 부른다. 그리고 1강의 javap 출력에서 봤던 그 수상한 public Hello(); 가 마침내 설명된다.