메서드 — 코드를 이름으로 묶는다
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;
}
- static13강에서 답한다
- int돌려줄 값의 타입
- add이름
- (int a, int b)받을 값들 — 매개변수
return 은 값을 돌려주고 그 자리에서 메서드를 끝낸다. return 아래의 코드는 실행되지 않는다.
부를 때는 이름과 괄호를 쓴다.
int sum = add(3, 4); // sum 은 7
넘긴 3 과 4 를 인자, 받는 쪽의 a 와 b 를 매개변수라고 부른다. 구분해서 알아두면 에러 메시지를 읽기 편하다.
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강에서 배운 조건문과 짝을 이루는 흔한 패턴이다. 잘못된 입력을 앞에서 걸러내고 나머지를 편하게 쓴다.
콜 스택 — 부른 곳으로 돌아가는 법
main 이 greet 를 부르고, greet 가 println 을 부른다. 그런데 println 이 끝나면 어디로 돌아가야 할까?
JVM 은 메서드를 부를 때마다 접시를 쌓듯이 기록을 쌓는다. 이것을 콜 스택이라고 한다.
- println()
- greet()
- main()
Call Stack
맨 위가 지금 실행 중인 메서드다. 끝나면 접시를 치우고, 바로 아래 접시가 이어서 돈다.
- 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 은 왜 안 됐나
이제 맨 처음 문제로 돌아간다.
자바는 값을 넘길 때 언제나 복사한다. 예외가 없다.
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 는 처음부터 아무 일도 없었다.
자바에서 메서드는 변수 자체를 넘겨받을 수 없다. 그래서 값 두 개를 진짜로 맞바꾸고 싶다면 다른 방법을 찾아야 한다. 배열에 담아 넘기거나, 바뀐 값을 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
이번엔 바뀌었다. 방금 "언제나 복사한다"고 해놓고 말이 다른 것 같다.
말은 그대로다. 복사된 것은 주소다.
주소가 복사됐다. 가리키는 곳은 하나다
- a (main)
- 0x1A4F
- → [ 1, 2, 3 ]
- arr (fill)
- 0x1A4F
- → 같은 곳
4강에서 int[] b = a; 로 복사했을 때와 똑같다. 메서드 호출도 결국 대입이다.
그럼 fill 안에서 배열을 통째로 갈아치우면 어떻게 될까.
static void reassign(int[] arr) {
arr = new int[]{ 7, 7, 7 };
}
- 1
안 바뀐다. arr 이라는 복사된 상자에 새 주소를 덮어썼을 뿐이다. main 의 a 는 여전히 옛 주소를 들고 있다.
오버로딩 — 같은 이름, 다른 재료
이름이 같아도 매개변수가 다르면 여러 개를 둘 수 있다.
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
컴파일러가 인자의 타입과 개수를 보고 어느 것을 부를지 고른다. 실행하기 전에 이미 정해진다.
println 이 int 든 String 이든 배열이든 다 받아주는 것도 오버로딩 덕분이다. 우리가 모르는 사이 수십 개가 준비되어 있었다.
반환 타입만 다른 것은 오버로딩이 아니다. 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 인 곳에서 부를 수 없다고 한다. main 이 static 이라서 생긴 일이다.
2강에서 "static 은 객체를 만들지 않고도 부를 수 있다는 뜻"이라고만 하고 넘어갔다. 지금은 main 과 짝을 맞추려고 붙였다고만 알아두자.
이 에러는 11강에서 객체를 배우고 13강에서 static 을 배우면 저절로 풀린다. 자바를 시작한 사람이 첫 주에 가장 많이 만나는 에러이기도 하다.
정리하면
메서드는 코드에 이름을 붙인 것이고, 자바는 넘기는 값을 언제나 복사한다.
- 01
반환 타입 · 이름 · 매개변수
돌려줄 게 없으면 void. 2강의 그 void 다.
static int add(int a, int b) { return a + b; } - 02
return은 값을 주고 즉시 끝낸다void 메서드에서도 return; 으로 빠져나올 수 있다.
- 03
메서드를 부르면 콜 스택에 쌓인다
끝나면 치워지고 부른 곳으로 돌아간다. 너무 쌓으면 StackOverflowError.
- 04
인자는 언제나 복사된다
기본형은 값이, 참조형은 주소가 복사된다. 그래서 swap 은 안 되고 fill 은 된다.
swap(a, b); // a, b 안 바뀐다 fill(arr); // arr 의 내용은 바뀐다 reassign(arr); // arr 은 안 바뀐다 - 05
이름이 같아도 매개변수가 다르면 된다
오버로딩. 반환 타입만 다른 것은 안 된다.
Part 3 이 끝났다. 조건으로 길을 나누고, 반복으로 같은 길을 여러 번 걷고, 배열로 값을 묶고, 메서드로 코드에 이름을 붙였다.
여기까지가 대부분의 언어에 다 있는 것들이다. 문법만 조금씩 다르다.
다음 Part 부터가 자바다. 지금까지 우리는 값과 코드를 따로 다뤘다. 값과 그 값을 다루는 코드를 한 덩어리로 묶으면 무슨 일이 벌어지는지 본다.
그 덩어리를 객체라고 부른다. 그리고 1강의 javap 출력에서 봤던 그 수상한 public Hello(); 가 마침내 설명된다.