클래스라는 틀 안에는 세 가지를 적는다. 객체가 계속 들고 있을 값을 담는 칸, 객체를 처음 만들 때 그 칸을 채우는 코드, 객체가 할 수 있는 동작이다. 값을 담는 칸은 객체가 살아 있는 동안 내내 남는다. 반면 동작 안에서 잠깐 만든 변수는 그 동작이 끝나면 사라진다. 이 둘을 헷갈려 같은 이름으로 쓰면, 객체의 값을 바꿨다고 생각했는데 잠깐 쓰는 변수만 바꾸고 끝나는 일이 생긴다.
예시로 보기
지금까지의 변수는 모두 main 안에 있었고, main이 끝나면 함께 사라졌다. 객체는 다르다. 학생 객체는 평균을 한 번 낸 뒤에도 이름과 점수를 계속 들고 있어야 한다. 그래서 변수를 둘 자리가 두 군데로 갈린다.
class Wallet {
int total; // 객체 안에 있는 칸
void earn(int n) {
int doubled = n * 2; // 이 메서드가 도는 동안만 있는 변수
total += doubled;
}
}
Wallet w = new Wallet();
w.earn(1);
w.earn(2);
System.out.println(w.total); // 6
earn(1)이 끝나면 doubled(값 2)는 사라진다. total에 더한 2는 객체에 남는다. 그래서 두 번째 호출이 4를 더해 6이 된다. 객체에 남는 total 같은 칸을 필드, 메서드 안에서 잠깐 쓰는 doubled 같은 변수를 지역 변수라고 부른다1.
클래스의 두 쓰임
클래스는 쓰임에 따라 둘로 나뉜다.
- 라이브러리 클래스: 혼자서는 실행할 수 없고 다른 클래스가 가져다 쓴다. 예:
Student. - 실행 클래스:
main()메서드가 있어 실행할 수 있다. 예:Student객체를 만들어 평균을 찍는GradeProcessor.
프로그램 하나에 실행 클래스는 보통 하나이고, 나머지는 그 클래스가 가져다 쓰는 라이브러리 클래스다1.
클래스의 멤버
클래스 안에 적는 것을 멤버라고 한다. 세 가지다.
public class ClassName {
// 필드
int fieldName;
// 생성자
ClassName() { ... }
// 메서드
int methodName() { ... }
}
| 멤버 | 하는 일 | 적는 모양 |
|---|---|---|
| 필드(field) | 객체의 자료를 담는다 | 변수 선언과 비슷하지만 쓰임이 다르다 |
| 생성자(constructor) | new로 객체를 만들 때 객체를 처음 상태로 채운다 |
메서드와 비슷하지만 돌려주는 타입이 없고, 이름이 클래스 이름과 같다 |
| 메서드(method) | 객체가 할 수 있는 동작이다. 함수라고도 부른다 | 돌려주는 타입, 이름, 매개변수, 본문 |
생성자는 생성자에서 따로 다룬다.
필드와 지역 변수
필드와 지역 변수는 이렇게 다르다.
| 필드 | 지역 변수 | |
|---|---|---|
| 선언하는 곳 | 클래스 블록 안(메서드 밖) | 생성자·메서드 블록 안 |
| 있는 곳 | 객체 안 | 생성자·메서드가 불려 도는 동안만 있다 |
| 쓸 수 있는 곳 | 객체 안팎 어디서나 | 그 생성자·메서드 블록 안에서만 |
표의 “객체 안팎 어디서나”는 필드를 감추지 않았을 때의 이야기이고, private를 붙이면 밖에서는 못 쓴다(접근 제한자).
차이가 하나 더 있다. 필드는 값을 주지 않아도 기본값으로 채워진다. 지역 변수는 값을 주지 않고 읽으면 번역 단계에서 막힌다1.
class Student {
String name; // null
int[] grades; // null
int year; // 0
boolean enrolled; // false
}
void m() {
int local;
System.out.println(local);
// error: variable local might not have been initialized
}
메서드 오버로딩
한 클래스에 이름이 같은 메서드를 여러 개 둘 수 있다. 단, 매개변수의 타입이나 개수나 순서가 달라야 한다.
class Calculator {
int add(int x, int y) { return x + y; }
double add(double x, double y) { return x + y; }
int add(int x, int y, int z) { return x + y + z; }
}
add(1, 2)는 3, add(1.5, 2.5)는 4.0, add(1, 2, 3)은 6이다. 번역기가 넘긴 값의 타입과 개수를 보고 맞는 것을 고른다.
돌려주는 타입만 다르게 해서는 안 된다. int add(int x, int y) 옆에 double add(int a, int b)를 두면 method add(int,int) is already defined 오류가 난다. 부르는 쪽 코드 add(1, 2)만 봐서는 둘 중 무엇을 뜻하는지 가릴 수 없기 때문이다. 매개변수 이름만 바꾼 것도 같은 이유로 안 된다1.
활용
- 필드 이름과 같은 이름의 지역 변수나 매개변수를 만들면, 그 블록 안에서는 지역 변수가 필드를 가린다. 필드를 가리키려면 앞에
this.를 붙인다. 생성자에서this.name = name;이라고 쓰는 까닭이다(생성자). - 흔한 실수: 메서드 안에서
int total = 0;처럼 타입을 다시 붙이는 것. 필드에 값을 넣는 것이 아니라 같은 이름의 지역 변수를 새로 만든다. System.out.println은int,double,String등을 받는 메서드가 따로 있는 오버로딩이다. 그래서 무엇을 넘겨도 같은 이름으로 부른다1.
연결
- 선수: 클래스와 객체, 변수와 기본 타입
- 객체가 아니라 클래스에 하나만 있는 필드와 메서드: static 멤버
- 오버로딩과 이름이 비슷한 다른 것: 객체 지향의 세 원칙의 다형성 두 갈래
확인 문제
C1 클래스의 멤버 세 가지와 각각이 하는 일을 쓰라. 라이브러리 클래스와 실행 클래스는 무엇으로 가르는가?
답: 필드는 객체의 자료를 담는다. 생성자는 new로 객체를 만들 때 객체를 처음 상태로 채운다. 메서드는 객체가 할 수 있는 동작이다. main() 메서드가 있어 실행할 수 있으면 실행 클래스, 없어서 다른 클래스가 가져다 쓰기만 하면 라이브러리 클래스다.
이유: 생성자를 메서드의 한 종류로 묶어 “둘”이라고 답하기 쉽다. 생성자는 돌려주는 타입이 없고 new와 함께만 불린다.
C2 필드와 지역 변수를 선언하는 곳, 있는 곳, 값을 주지 않았을 때로 견주어라.
답: 필드는 클래스 블록 안(메서드 밖)에 선언하고 객체 안에 있으며, 값을 주지 않으면 기본값(숫자 0, boolean은 false, 참조 타입은 null)이 들어간다. 지역 변수는 생성자·메서드 블록 안에 선언하고 그 블록이 도는 동안만 있으며, 값을 주지 않고 읽으면 컴파일 오류다.
이유: “지역 변수도 0으로 시작한다”가 흔한 오답이다. variable local might not have been initialized 오류가 난다.
C3 class Box { int count; void put() { int count = 0; count++; this.count += 2; } }에서 Box b = new Box(); b.put(); b.put(); 뒤 b.count는?
답: 4
이유: put 안의 int count = 0;은 필드와 이름이 같은 지역 변수다. count++는 이 지역 변수를 1로 만들 뿐이고, 메서드가 끝나면 사라진다. 필드에 닿는 것은 this.count += 2뿐이라 두 번 불러 4다. 흔한 오답 6은 count++도 필드에 더한 것으로 센 것이다1.