1. 접근 제어자 이해
자바는 public, private 같은 접근 제어자를 제공합니다. 접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드에 접근하는 것을 허용하거나 제한할 수 있습니다. 이런 접근제어자가 왜 필요한지 예시를 통해 이유를 알아보겠습니다.
예시)
제가 만약 스피커를 만드는 SW개발자라고 하겠습니다.
또한 스피커의 음량은 절대 100을 넘으면 안되는 요구사항이 있다고 가정하겠습니다. (100을 넘어가면 스피커의 부품들이 고장난다.)
요구사항
- 스피커 객체 만들기
- 스피커는 음량을 높이고, 내리고, 현재 음량 확인 기능이 존재한다.
- 스피커의 음량은 최대 100까지만 증가할 수 있다. 초과 불가.
Speaker
package access;
public class Speaker {
int volume;
//생성자
Speaker(int volume) {
this.volume = volume;
}
void volumeUp() {
if (volume >= 100) {
System.out.println("음량을 증가할 수 없습니다. 최대 음량입니다.");
} else {
volume += 10;
System.out.println("음량을 10 증가합니다.");
}
}
void volumeDown() {
volume -= 10;
System.out.println("volumeDown 호출");
}
void showVolume() {
System.out.println("현재 음량:" + volume);
}
}
SpeakerMain
package access;
public class SpeakerMain {
public static void main(String[] args) {
Speaker speaker = new Speaker(90);
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
}
}
//출력 결과
현재 음량: 90
음량을 10 증가합니다.
현재 음량: 100
음량을 증가할 수 없습니다. 최대 음량입니다.
현재 음량: 100
이렇게 출력 결과가 나올 수 있습니다. 이후 시간이 흘러서 다음 버전의 스피커를 출시하게 되었지만, 새로운 개발자는 기존 요구사항을 잘 몰랐고, 소리를 더 올리면 좋겠다고 생각했습니다. 그래서 밑과 같은 코드를 사용했습니다.
SpeakerMain - 필드 직접 접근 코드 추가
package access;
public class SpeakerMain {
public static void main(String[] args) {
Speaker speaker = new Speaker(90);
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
speaker.volumeUp();
speaker.showVolume();
//필드에 직접 접근
System.out.println("volume 필드 직접 접근 수정");
speaker.volume = 200;
speaker.showVolume();
}
}
//출력 결과
현재 음량: 90
음량을 10 증가합니다.
현재 음량: 100
음량을 증가할 수 없습니다. 최대 음량입니다.
현재 음량: 100
volume 필드 직접 접근 수정
현재 음량: 200
이렇게 Speaker 객체를 사용하는 사용자는 Speaker의 volume 필드와 메서드에 모두 접근할 수 있었습니다. 따라서 volume을 200으로 설정하는 순간 스피커의 부품들에 과부하가 걸리면서 폭발하게 되었습니다.
초기 개발자는 volumeUp()메서드를 만들어서 음량을 100을 못넘기게 했지만 소용이 없었습니다. 왜냐하면 Speaker를 사용하는 입장에서는 volume필드에 직접적으로 접근할 수 있었기 때문입니다.
이런 문제를 해결하기 위해서 volume 필드의 외부 접근을 막을 수 있는 방법이 필요했습니다. 이것이 접근 제어자가 필요한 이유입니다.
Speaker - volume 접근 제어자를 private로 수정
package access;
public class Speaker {
private int volume; //private 사용
...
}
private 접근 제어자는 모든 외부 호출을 막습니다. 따라서 private이 붙은 경우 해당 클래스 내부에서만 호출할 수 있습니다.
이제 SpeakerMain 코드를 다시 실행해보겠습니다.
//필드에 직접 접근
System.out.println("volume 필드 직접 접근 수정");
speaker.volume = 200;
speaker.showVolume();
이렇게 실행하게 되면 IDE에서 speaker.volume = 200 부분에 오류가 생기게 되고, 실행을해보면 컴파일 오류가 발생하게 됩니다.
그리고 speaker.volume을 주석 처리하고 실행하게 되면 정상적으로 작동하게 됩니다.
만약 Speaker 클래스를 개발하는 개발자가 처음부터 private를 사용해서 volume 필드의 외부 접근을 막았다면 새로운 개발자도 volume 필드에 직접 접근하지 않고, volumeUp()과 같은 메서드를 통해 접근했을 것입니다. 결과적으로 Speaker가 폭발하는 문제는 발생하지 않았을 것입니다.
2. 접근 제어자 종류
자바는 4가지의 접근 제어자가 있습니다.
- private : 모든 외부 호출을 막습니다. (같은 클래스 안에서의 호출은 허용한다.)
- default (package-private) : 같은 패키지안에서 호출은 허용합니다.
- protected : 같은 패키지안에서 호출은 허용된다. 패키지가 달라도 상속 관계의 호출은 허용합니다. 해당 클래스를 상속받지 않은 외부 패키지의 클래스에서는 접근이 불가능합니다.
- public : 모든 외부 호출을 허용합니다.
package-private
접근제어자를 명시하지 않으면 같은 패키지 안에서 호출을 허용하는 default 접근 제어자가 적용됩니다.
default라는 용어는 해당 접근 제어자가 기본값으로 사용되기 때문에 붙여진 이름이지만, 실제로는 package-private가 더 정확한 표현입니다. 왜냐하면 해당 접근 제어자를 사용하는 멤버는 동일한 패키지 내의 다른 클래스에서만 접근이 가능하기 때문입니다. 두 용어는 혼용해서 사용합니다.
접근 제어자 사용 위치
접근 제어자는 필드와 메서드, 생성자,클래스에 사용됩니다.
접근 제어자 예시
public class Speaker { //클래스 레벨
private int volume; //필드
public Speaker(int volume) {} //생성자
public void volumeUp() {} //메서드
public void volumeDown() {}
public void showVolume() {}
}
접근 제어자 핵심
- private는 나의 클래스 안으로 속성과 기능을 숨길 때 사용, 외부 클래스에서 해당 기능을 호출할 수 없습니다.
- default는 나의 패키지 안으로 속성과 기능을 숨길 때 사용, 외부 패키지에서 해당 기능을 호출할 수 없습니다.
- protected는 상속 관계로 속성과 기능을 숨길 때 사용, 상속 관계가 아닌 곳에서 해당 기능을 호출할 수 없습니다.
- public은 기능을 숨기지 않고 어디서든 호출할 수 있게 공개됩니다.
3. 접근 제어자 사용 - 필드, 메서드
그렇다면 다양한 상황에 따른 접근 제어자를 확인해보겠습니다.
AccessData
package access.a;
public class AccessData {
public int publicField;
int defaultField;
private int privateField;
public void publicMethod() {
System.out.println("publicMethod 호출 "+ publicField);
}
void defaultMethod() {
System.out.println("defaultMethod 호출 " + defaultField);
}
private void privateMethod() {
System.out.println("privateMethod 호출 " + privateField);
}
public void innerAccess() {
System.out.println("내부 호출");
publicField = 100;
defaultField = 200;
privateField = 300;
publicMethod();
defaultMethod();
privateMethod();
}
}
위 코드는 순서대로 public, default, private를 필드와 메서드에 사용했습니다. 마지막에 innerAccess()가 있는데, 이 메서드는 내부 호출을 보여줍니다. 내부 호출은 자기 자신에게 접근하는 것입니다. 따라서 private를 포함한 모든 곳에 접근할 수 있습니다.
이제 외부에서 이 클래스로 접근해보겠습니다.
AccessInnerMain
package access.a;
public class AccessInnerMain {
public static void main(String[] args) {
AccessData data = new AccessData();
//public 호출 가능
data.publicField = 1;
data.publicMethod();
//같은 패키지 default 호출 가능
data.defaultField = 2;
data.defaultMethod();
//private 호출 불가
//data.privateField = 3;
//data.privateMethod();
data.innerAccess();
}
}
//출력 결과
publicMethod 호출 1
defaultMethod 호출 2
내부 호출
publicMethod 호출 100
defaultMethod 호출 200
privateMethod 호출 300
외부에서 AccessData클래스에 접근을 위 코드와 같이 실행할 수 있습니다.
- public은 모든 접근을 허용하기 때문에 필드, 메서드 모두 접근이 가능합니다.
- default는 같은 패키지에서 접근할 수 있습니다. 만약 access.b.AccessInnerMain가 있다면 access.a.AccessInnerData와 다른 패키지이므로 default 접근자로 접근할 수 없습니다.
- private는 AccessData 내부에서만 접근할 수 있습니다. 따라서 호출할 수 없습니다.
- AccessData.innerAccess() 메서드는 public입니다. 따라서 외부에서 호출할 수 있습니다. innerAccess() 메서드는 외부에서 호출되었지만 해당 메서드 안에서는 자신의 private 필드와 메서드에 접근할 수 있습니다.
4. 접근 제어자 사용 - 클래스 레벨
4-1. 클래스 레벨의 접근 제어자 규칙
- 클래스 레벨의 접근 제어자는 public, default만 사용할 수 있습니다.
- private, protected는 사용할 수 없습니다.
- public 클래스는 자신의 파일명과 이름이 같아야 합니다.
- 하나의 자바 파일에 public 클래스는 하나만 등장할 수 있습니다.
- 하나의 자바 파일에 default 접근 제어자를 사용하는 클래스는 무한정 만들 수 있습니다.
PublicClass.java파일
package access.a;
public class PublicClass {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
DefaultClass1 class1 = new DefaultClass1();
DefaultClass2 class2 = new DefaultClass2();
}
}
class DefaultClass1 {
}
class DefaultClass2 {
}
private class, protected class가 안되는 이유에 대한 개인적인 생각
private class를 만들면 클래스에 대한 접근 자체가 안됩니다. 즉, '만드는 이유가 없기 때문에 안만들어졌다.' 라고 생각합니다.
protected class가 안되는 이유도 마찬가지입니다.
이유1. 다른 패키지 하위 클래스에서 상위 클래스를 상속받아서 하위 클래스에서 확장성 목적으로 무언가 만들어 져야 하는데 하위 클래스에서 상속 받는 순간 상위 클래스의 역할은 없어지게 됩니다.
이유2. protected클래스를 상속 받을 수 있는 키워드가 없습니다.
따라서 '만드는 이유가 없기 때문에 안만들어졌다.'라고 생각합니다.
PublicClassInnerMain
package access.a;
public class PublicClassInnerMain {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
DefaultClass1 class1 = new DefaultClass1();
DefaultClass2 class2 = new DefaultClass2();
}
}
PublicClassInnerMain과 DefaultClass1, DefaultClass2는 같은 패키지입니다. 따라서 접근할 수 있습니다.
PublicClassOuterMain
package access.b;
//import access.a.DefaultClass1;
import access.a.PublicClass;
public class PublicClassOuterMain {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
//다른 패키지 접근 불가
//DefaultClass1 class1 = new DefaultClass1();
//DefaultClass2 class2 = new DefaultClass2();
}
}
PublicClassOuterMain과 DefaultClass1, DefaultClass2는 다른 패키지입니다. 따라서 접근할 수 없습니다.
5. 캡슐화
캡슐화는 객체 지향 프로그래밍의 중요한 개념 중 하나입니다. 캡슐화는 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어서 외부에서의 접근을 제한하는 것을 말합니다. 캡슐화를 통해 데이터의 직접적인 변경을 방지하거나 제한할 수 있습니다.
쉽게 이야기해서 속성과 기능을 하나로 묶고, 외부에 꼭 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것을 말합니다.
위에서 설명드린 접근제어자를 통해서 캡슐화를 더욱 안전하게 완성할 수 있습니다.
'Language > Java' 카테고리의 다른 글
[Java] 패키지 (2) | 2023.12.18 |
---|---|
[Java] 생성자 (0) | 2023.12.18 |
[Java] 절차지향, 객체 지향 프로그래밍 (0) | 2023.12.17 |
[Java] 변수와 초기화 (0) | 2023.12.17 |
[Java] 인자, 매개변수 (1) | 2023.12.14 |