2. 캡슐화(Encapsulation)
📌 Goal
- 캡슐화의 핵심 개념과 목적을 이해하고 설명할 수 있다.
- 패키지의 개념과 import문이 어떻게 사용되는 지 이해할 수 있다.
- 자바에서 캡슐화를 달성하기 위핸 핵심적인 수단으로 접근제어자 네 가지를 이해하고, 각각의 접근 가능 범위를 설명할 수 있다.
- 데이터를 효과적으로 보호하기 위한 수단으로 getter/setter 메서드를 이해하고 사용할 수 있다.
📌 캡슐화(Encapsulation)
특정 객체 안에 관련된 속성과 기능을 하나의 캡슐(capsulate)로 만들어 데이터를 외부로부터 보호함
✔️ 캡슐화의 목적
외부로부터 객체의 속성과 기능을 함부로 변경하지 못하게 막고, 데이터가 변경되더라도 다른 객체에 영향 다른 객체에 영향을 주지 않기에 독립성을 확보할 수 있다. 더 나아가 유지보수와 코드 확장 시에도 오류의 범위를 최소화할 수 있어서 효과적으로 코드를 유지보수하기에 용이하다.
✔️ 캡슐화를 수행하기 위한 핵심 수단
1. 접근제어자(Access Modifier)
2. getter/setter 메서드
📌 접근 제어자(Access Modifier)
✔️ 제어자(Modifier)
클래스, 필드, 메서드, 생성자 등에 부가적인 의미를 부여하는 키워드이다.
'파란 하늘', '붉은 노을'이라는 단어에서 '파란'과 '붉은'처럼 명사를 꾸며주는 형용사와 역할이 비슷하다.
자바의 제어자는 크게 접근 제어자와 기타 제어자로 구분할 수 있다.
제어자 | 종류 |
접근 제어자 | public, protected, (default), private |
기타 제어자 | static, final, abstract, native, transient, synchronized |
제어자는 클래스, 필드, 메서드, 생성자 등에 주로 사용되며 '맛있는 빨간 사과'에서 사과를 수식하기 위해 '맛있는'과 '빨간'이라는 형용사가 두 번 사용된 것처럼 하나의 대상에 대해서 여러 제어자를 사용할 수 있다.
✔️ 접근 제어자(Access Modifier)
객체 지향에서 정보 은닉(data hiding)이란 사용자가 굳이 알 필요가 없는 정보는 사용자로부터 숨겨야 한다는 개념이다. 자바에서는 이러한 정보 은닉을 위해 접근 제어자(Access Modifier)라는 기능을 제공한다.
❗️자바 접근 제어자 종류
접근 제어자 | 동일 클래스 내 | 동일 패키지 내 | 다른 패키지의 하위 클래스 | 패키지 외 전체 |
Private | O | X | X | X |
Default | O | O | X | X |
Protected | O | O | O | X |
Public | O | O | O | O |
❗️같은 패키지, 동일 클래스 vs 같은 패키지 다른 클래스
package package1; // 패키지명 package1
//파일명: Parent.java
class Test { // Test 클래스의 접근 제어자는 default
public static void main(String[] args) {
Parent p = new Parent();
//System.out.println(p.a); // 동일 클래스가 아니기 때문에 에러발생!
System.out.println(p.b);
System.out.println(p.c);
System.out.println(p.d);
}
}
public class Parent { // Parent 클래스의 접근 제어자는 public
// a,b,c,d에 각각 private, default, protected, public 접근제어자 지정
private int a = 1;
int b = 2;
protected int c = 3;
public int d = 4;
public void printEach() { // 동일 클래스이기 때문에 에러발생하지 않음
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
// 출력값
2
3
4코드를 입력하세요
Parent 클래스 내에 존재하는 printEach() 메서드를 통해 a,b,c,d(a : private, b : default, c : protected, d : public)를 접근해보면 모두 동일 클래스 내에서 접근을 시도했기 때문에 모든 변수에 대해 접근이 가능하다.
반면에 main 메서드에서 선언한 Parent 클래스 p를 통해 a,b,c,d를 접근했을 때 동일 클래스가 아니기 때문에 private 접근 제어자가 달린 a에서 에러가 발생한다.
❗️다른 패키지, 상속받은 클래스 vs 다른 패키지, 다른 클래스
package package2; // package2
//파일명 Test2.java
import package1.Parent;
class Child extends package1.Parent { // package1으로부터 Parent 클래스를 상속
public void printEach() {
// System.out.println(a); // 에러 발생!
// System.out.println(b);
System.out.println(c); // 다른 패키지의 하위 클래스
System.out.println(d);
}
}
public class Test2 {
public static void main(String[] args) {
Parent p = new Parent();
// System.out.println(p.a); // public을 제외한 모든 호출 에러!
// System.out.println(p.b);
// System.out.println(p.c);
System.out.println(p.d);
}
}
다른 패키지에서 접근하는 경우 어떨까? 먼저 Child 클래스가 package1의 Parent 클래스를 상속받았기 때문에 a와 b에서는 에러가 나지만 Protected와 Public으로 선언한 변수들은 정상적으로 출력이 된다.
마지막으로 다른 패키지의 다른 클래스인 Test2는 Public 접근 제어자인 d만 출력이 되는 것을 알 수 있다. 이러한 접근 제어자를 통해 외부로부터 데이터를 보호하고 불필요하게 데이터가 노출되는 것을 방지할 수 있다.
✔️ 기타 제어자(참고용, 캡슐화에 대한 내용은 아님)
❗️ final 제어자
final 제어자는 '변경할 수 없다'라는 의미로 사용된다.
- 필드나 지역 변수에 사용하면 값을 변경할 수 없는 상수(constant)가 된다.
- 클래스에 사용하면 해당 클래스는 다른 클래스가 상속받을 수 없게 된다.
- 메서드에 사용하면 해당 메소드는 오버라이딩(overriding)을 통한 재정의를 할 수 없게 된다.
❗️static 제어자
static 제어자는 '공통적인'이라는 의미로 사용된다.
- 변수에 사용하면 해당 변수를 클래스 변수로 만들어준다.
- 메소드에 사용하면 해당 메서드를 클래스 메서드로 만들어준다.
- 멤버에 사용하면 다음과 같은 특징을 가진다.
- ▶ 프로그램 시작 시 최초에 단 한 번만 생성되고 초기화 된다.
- ▶ 인스턴스를 생성하지 않고도 바로 사용할 수 있다.
- ▶ 해당 클래스의 모든 인스턴스가 공유된다.
- (싱글턴 패턴)메서드, 필드, 초기화 블록에서 사용할 수 있다.
❗️abstract 제어자
abstact 제어자는 '추상적인'이라는 의미로 사용된다.
- 선언부만 있고 구현부가 없는 메서드를 추상 메서드라고 하며, 반드시 abstract 제어자를 붙여야 한다.
- 하나 이상의 추상 메서드가 포함하고 있는 추상 클래스도 반드시 abstract 제어자를 붙여야한다.
- 클래스와 메서드에서 사용할 수 있다.
📌 getter/setter 메서드
getter/setter 메서드를 사용하면 객체지향의 캡슐화의 목적을 달성하면서도 데이터를 변경할 수 있다.
다음 예제를 살펴보자.
public class GetterSetterTest {
public static void main(String[] args) {
Worker w = new Worker();
w.setName("김코딩");
w.setAge(30);
w.setId(5);
String name = w.getName();
System.out.println("근로자의 이름은 " + name);
int age = w.getAge();
System.out.println("근로자의 나이는 " + age);
int id = w.getId();
System.out.println("근로자의 ID는 " + id);
}
}
class Worker {
private String name; // 변수의 은닉화. 외부로부터 접근 불가
private int age;
private int id;
public String getName() { // 멤버변수의 값
return name;
}
public void setName(String name) { // 멤버변수의 값 변경
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age < 1) return;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
// 출력값
근로자의 이름은 김코딩
근로자의 나이는 30
근로자의 ID는 5
Worker 클래스의 인스턴스 변수는 각각 private 접근 제어자로 선언되어 있고, private 접근제어자는 앞서 설명하였듯이 같은 패키지, 다른 패키지에서 해당 클래스를 상속받은 클래스에서만 접근이 가능하다. 만약 다른 패키지에서 다른 클래스가 해당 클래스의 변수를 접근해야한다고 한다면, 이 때 사용되는 것이 getter/setter 메소드이다.
✔️ setter 메서드
외부에서 메서드에 접근하여 조건에 맞을 경우 데이터의 값을 변경 가능하게 해주고 일반적으로 접근하고자 하는 변수명에 set-을 붙여서 정의한다.
✔️ getter 메서드
설정된 변수의 값을 읽어오는 데 사용되는 메서드이다. 일반적으로 접근하고자 하는 변수명에 get-을 붙여서 정의한다. 경우에 따라 객체 외부에서 필드 값을 사용하기에 부적절한 경우가 발생할 수 있는데 이러한 경우에 그 값을 가공한 이후에 외부로 전달하는 역할도 한다.