자바는 public, private 같은 접근 제어자를 제공하다. 접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드가 접근 하는 것을 허용하거나 제한할 수 있다.
클래스: 객체를 생성하기 위한 설계도,사용자 정의 자료형, 객체를 생성하기 위한 필드와 메소드의 집합


생성자: 클래스의 이름과 동일한 이름을 가진 메소드




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);
}
}
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();
}
}


Speaker 객체를 사용하는 사용자는 Speaker의 volumn 필드와 메서드에 모두 접근 할 수 있다. 100을 넘는 기능을 만들었지만 직접 값을 설정할 수 있어 소용이 없다. 외부 접근을 막을 수 있는 방법이 필요했다.


결론
Speaker 외부에서 volumn 필드에 직접 접근하는 것은 불가능하다. 이 경우 자바 컴파일러가 컴파일 오류를 발생시킨다. 외부 접근으로 막아둠으로써 새로운 개발자도 volumn 필드에 직접 접근하지 않고, volumeUp()과 같은 메서드를 통해서 접근 했을 것이다. 결과적으로 폭발하는 문제가 발생하지 않았을 것이다.
순서대로 private 이 가장 많이 차단하고, public 이 가장 많이 허용한다.
private -> default -> protected -> public
= default 접근 제어자
접근 제어자는 필드와 메서드, 생성자에 사용된다.

지역변수에는 쓸 수 없다.(어차피 지역 안에서만 쓸 수 있는 변수들이라서)
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();
}
}
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();
}
}
package access.b;
import access.a.AccessData;
public class AccessOuterMain {
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();
}
}
생성자도 접근 제어자 관점에서 메서드와 같다.
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 {
}
package access.a;
public class PublicClassInnerMain {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
DefaultClass1 class1 = new DefaultClass1();
DefaultClass2 class2 = new DefaultClass2();
}
}
public 클래스(외부에서 접근가능)라서 접근 가능하고 나머지 밑에 2개는 같은 패키지기 때문에 된다.
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();
}
}
쉽게 이야기해서 속성과 기능을 하나로 묶고, 외부에 꼭 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것.
객체의 데이터는 객체가 제공하는 기능인 메서드를 통해서 접근해야 한
다.
정리: 데이터는 모두 숨기고, 기능은 꼭 필요한 기능만 노출하는 것이 좋은 캡슐화이다.
BankAccount
package access;
public class BankAccount {
private int balance;
public BankAccount() {
balance = 0;
}
// public 메서드: deposit
public void deposit(int amount) {
if (isAmountValid(amount)) {
balance += amount;
} else {
System.out.println("유효하지 않은 금액입니다.");
}
}
// public 메서드: withdraw
public void withdraw(int amount) {
if (isAmountValid(amount) && balance - amount >= 0) {
balance -= amount;
} else {
System.out.println("유효하지 않은 금액이거나 잔액이 부족합니다.");
}
}
// public 메서드: getBalance
public int getBalance() {
return balance;
}
// private 메서드: isAmountValid
private boolean isAmountValid(int amount) {
// 금액이 0보다 커야함
return amount > 0;
}
}
package access;
public class BankAccountMain {
public static void main(String[] args) {
BankAccount account = new BankAccount();
account.deposit(10000);
account.withdraw(3000);
System.out.println("balance = " + account.getBalance());
}
BankAccount를 사용하는 개발자 입장에서는 사용할 수 있는 메서드가 하나 더 늘었다. 입금과 출금전에 isAmountValid()를 사용해서 검증을 해야 하나? 라고 의문을 가질 것이다.
이 필드를 직접 사용해도 된다고 판단하고 쓰면 모든 검증과 캡슐화가 깨지고 잔고를 무한정 늘리고 출금하는 심각한 문제가 발생할 수 있다.
private : 쓰면 안됨, public : 갖다 씀.

package access.ex;
public class CounterMain {
public static void main(String[] args) {
MaxCounter counter = new MaxCounter(3);
counter.increment();
counter.increment();
counter.increment();
counter.increment();
int count = counter.getCount();
System.out.println(count);
}
}

답:
package access.ex;
public class MaxCounter {
private int count = 0;
private int max;
public MaxCounter(int max) {
this.max = max;
}
public void increment() {
//검증로직
if (count >= max) {
System.out.println("최대값을 초과할 수 없습니다.");
return;
}
//실행 로직
count++;
}
public int getCount() {
return count;
}
}
핵심
- 데이터들은 private(숨김), 외부기능들은 public

package access.ex;
public class ShoppingCartMain {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
Item item1 = new Item("마늘", 2000, 2);
Item item2 = new Item("상추", 3000, 4);
cart.addItem(item1);
cart.addItem(item2);
cart.displayItems();
}
Item 클래스
package access.ex;
public class Item {
private String name;
private int price;
private int quantity;
//TODO 나머지 코드를 완성해라.
}
ShoppingCart 클래스
package access.ex;
public class ShoppingCart {
private Item[] items = new Item[10];
private int itemCount;
//TODO 나머지 코드를 완성해라.
}
정답
속성과 기능이 근처에 있는 걸 쓰는 게 좋다. 수정할 때도 편하다.
package access.ex;
public class ShoppingCart {
private Item[] items = new Item[10];
private int itemCount;
public void addItem(Item item) {
if(itemCount >= items.length){
System.out.println("장바구니가 가득 찼습니다.");
return;
}
//else는 검증로직, 정상로직이 검증로직 안에 들어가는 것은 별로 맘에 안든다.
items[itemCount] = item;
itemCount++;
}
// 장바구니 상품 출력
// 상품명:마늘, 합계:4000
// 상품명:상추, 합계:12000
// 전체 가격 합:16000
public void displayItems() {
System.out.println("장바구니 상품 출력");
for (int i = 0; i < itemCount; i++) {
Item item = items[i];
System.out.println("상품명" + item.getName() + ", 합계: " + item.getTotalPrice());
}
System.out.println("전체 가격 합: " + calculateTotalPrice());
}
private int calculateTotalPrice() {
int totalPrice = 0;
for (int i = 0; i < itemCount; i++) {
Item item = items[i];
totalPrice += item.getTotalPrice();
}
return totalPrice;
}
}
package access.ex;
public class Item {
private String name;
private int price;
private int quantity;
public Item(String name, int price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
public String getName() {
return name;
}
public int getTotalPrice() {
return price * quantity;
}
}