1. Getter와 Setter란?
객체를 금고라고 생각해 봅시다. 금고의 비공개 필드는 금고 안의 내용물이고, getter와 setter는 각 칸의 열쇠입니다. getter는 안에 무엇이 있는지 알려 주고, setter는 그곳에 새로운 것을 조심스럽게 넣게 해 줍니다(하지만 문서 대신 고슴도치를 넣는 일은 피합시다).
Getter
Getter는 private 필드 값을 반환하는 public 메서드입니다. 보통 이름은 get + 필드 이름의 첫 글자를 대문자로 시작합니다.
public class Person {
private String name; // 비공개 필드
// name 필드의 getter
public String getName() {
return name;
}
}
Setter
Setter는 비공개 필드의 값을 변경할 수 있게 하는 public 메서드입니다. 이름은 set + 필드 이름의 첫 글자를 대문자로 시작합니다.
public class Person {
private String name;
// name 필드의 setter
public void setName(String name) {
this.name = name;
}
}
boolean 필드의 경우
boolean 타입 필드의 경우 getter에 is 접두사를 사용하는 것이 관례입니다:
private boolean active;
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
2. 문법과 명명 규칙
Java는 엄격하지만 꽉 막힌 언어는 아닙니다. 코드가 다른 개발자(그리고 한 달 뒤의 여러분)에게도 이해되도록 해 주는 확립된 규칙이 있습니다.
- Getter: public Type getFieldName()
- Setter: public void setFieldName(Type value)
- boolean의 Getter: public boolean isFieldName()
필드 이름은 메서드에서 첫 글자를 대문자로 씁니다. 예를 들어 필드가 age라면 메서드는 getAge()와 setAge()가 됩니다.
이 규칙은 JavaBeans 스타일을 따르며, 덕분에 IDE, 라이브러리, 프레임워크가 여러분의 getter와 setter를 자동으로 찾아낼 수 있습니다. 예를 들어 Spring이나 JavaFX를 사용한다면, 필요한 순간에 이러한 메서드가 마치 "마법"처럼 호출됩니다.
3. 코드 예시
학습용 프로젝트로 간단한 "연락처" 애플리케이션(전화번호부와 유사)을 가져와서 올바른 getter와 setter를 추가해 보겠습니다.
예시: 비공개 필드와 getter/setter를 가진 Contact 클래스
public class Contact {
private String name;
private String phone;
private int age;
private boolean favorite;
// Getter들
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public int getAge() {
return age;
}
public boolean isFavorite() {
return favorite;
}
// Setter들
public void setName(String name) {
this.name = name;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setAge(int age) {
// 검증 예시: 나이는 음수일 수 없음
if (age < 0) {
System.out.println("나이는 음수가 될 수 없습니다!");
return;
}
this.age = age;
}
public void setFavorite(boolean favorite) {
this.favorite = favorite;
}
}
애플리케이션에서의 사용
Contact friend = new Contact();
friend.setName("이반 이바노프");
friend.setPhone("+1-999-123-45-67");
friend.setAge(25);
friend.setFavorite(true);
System.out.println("이름: " + friend.getName());
System.out.println("전화: " + friend.getPhone());
System.out.println("나이: " + friend.getAge());
System.out.println("즐겨찾기: " + (friend.isFavorite() ? "예" : "아니오"));
setter에서의 검증 예시
setAge의 setter에서 간단한 검사를 추가한 점에 주목하세요. 나이가 음수이면 필드를 변경하지 않고 경고를 출력합니다. 이는 객체를 잘못된 데이터로부터 보호하는 간단한 방법입니다.
4. 모범 사례: 올바르게 작성하는 방법
모든 필드에 public setter가 필요하지는 않습니다
때로는 필드가 읽기 전용이어야 합니다. 예를 들어, 객체 생성 시 설정되고 이후 변경되지 않는 고유 식별자 같은 경우입니다. 이런 경우에는 setter를 작성하지 않습니다:
public class Contact {
private final int id; // 초기화 이후에는 변경 불가
public Contact(int id) {
this.id = id;
}
public int getId() {
return id;
}
// setId는 없습니다!
}
접근 제어와 검증을 위해 getter/setter를 사용하세요
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
System.out.println("이름은 비어 있을 수 없습니다!");
return;
}
this.name = name;
}
내부의 변경 가능한 객체를 직접 노출하지 마세요
예를 들어, 전화번호 목록 같은 필드가 있다면:
private String[] phones;
이를 getter로 그대로 반환해서는 안 됩니다:
public String[] getPhones() {
return phones; // 좋지 않음!
}
이런 코드는 외부에서 목록을 마음대로 변경할 수 있게 하여 캡슐화를 깨뜨립니다!
더 나은 방법: 배열의 복사본을 반환하세요:
public String[] getPhones() {
return Arrays.copyOf(phones, phones.length); // 복사본을 반환
}
또는 간단히 clone:
public String[] getPhones() {
return phones.clone();
}
getter와 setter는 이해하기 쉽고 단순하게
- 복잡한 비즈니스 로직을 getter/setter에 넣지 마세요. 이들의 역할은 단순합니다: 접근 제어와 필요 시 검증.
- 변경되어서는 안 되는 필드는 아예 setter를 만들지 마세요.
- 외부에서 접근할 수 없어야 하는 필드는 getter를 만들지 마세요.
5. IDE에서 getter/setter 자동 생성
필드가 열 개쯤 되는 클래스를 수동으로 작성하는 일은 꽤 번거롭습니다. 다행히 현대적인 IDE(IntelliJ IDEA, Eclipse, 플러그인을 사용한 VS Code 등)는 이를 자동으로 생성해 줍니다.
IntelliJ IDEA에서
- 클래스를 열고, 클래스 본문 안에 커서를 둡니다.
- Alt + Insert(또는 Code -> Generate...)를 누릅니다.
- Getter and Setter를 선택합니다.
- 원하는 필드를 체크하고 OK를 누릅니다.
짜잔! getter와 setter가 자동으로 생성됩니다.
Eclipse에서
- 클래스를 엽니다.
- 마우스 오른쪽 버튼 클릭 — Source — Generate Getters and Setters...
- 필드를 선택하고 OK를 누릅니다.
VS Code(Java Extension Pack 사용)에서
- 클래스 파일을 엽니다.
- 명령 팔레트(Ctrl+Shift+P)에서 Generate getters and setters를 입력합니다.
- 안내에 따라 진행합니다.
6. 애플리케이션 개선: 캡슐화 실전
이전 강의에서는 연락처를 저장하는 간단한 애플리케이션을 만들었습니다. 이제 필드를 비공개로 만들고 getter/setter를 통해서만 접근하도록 개선해 보겠습니다.
이전(나쁜 예):
public class Contact {
public String name;
public String phone;
public int age;
}
문제: 아무 코드나 이렇게 할 수 있습니다:
Contact c = new Contact();
c.age = -1000; // 이제 우리 전화번호부에 뱀파이어가 생겼습니다!
이후(좋은 예):
public class Contact {
private String name;
private String phone;
private int age;
public void setAge(int age) {
if (age < 0) {
System.out.println("나이는 음수가 될 수 없습니다!");
return;
}
this.age = age;
}
public int getAge() {
return age;
}
// 나머지 getter/setter...
}
이제는 외부에서 우연히(혹은 의도적으로) 객체를 망가뜨리기 어렵습니다.
7. 계산되거나 변경 불가능한 속성의 getter/setter
값을 필드에 저장하지 않고, 즉시 계산하는 경우도 있습니다:
public class Rectangle {
private int width;
private int height;
public int getArea() {
return width * height;
}
}
면적에 대한 setter는 필요 없습니다 — 직접 설정할 수 없고, 너비나 높이를 변경해야만 바뀝니다.
8. Getter와 Setter: 흔한 실수
실수 №1: getter/setter가 캡슐화를 깨뜨림.
getter가 내부의 변경 가능한 객체(예: 리스트)에 대한 참조를 반환하면, 외부 코드가 모든 검사를 우회해 이를 변경할 수 있습니다. 이는 캡슐화의 취지에 어긋납니다.
실수 №2: setter가 데이터를 검증하지 않음.
setter가 아무 검사 없이 값을 할당하기만 하면, 잘못된 객체 상태(예: 음수 나이, 빈 이름)를 초래할 수 있습니다.
실수 №3: 모든 필드에 대해 setter를 자동 생성.
IDE가 모든 필드에 대한 setter를 생성해 줄 수 있지만, 항상 옳은 것은 아닙니다! 예를 들어 식별자(id)에는 setter가 필요 없습니다.
실수 №4: getter/setter에 복잡한 로직을 넣음.
getter와 setter는 단순해야 합니다. 복잡한 비즈니스 로직이 필요하다면 별도의 메서드로 분리하세요.
실수 №5: 명명 규칙을 어김.
getter를 getName() 대신 fetchName()으로 이름 지으면, 일부 프레임워크와 라이브러리는 이를 인식하지 못할 수 있습니다.
GO TO FULL VERSION