기능이 점점 추가되어서 프로그램이 아주 커진다면?
-> 매우 많은 클래스가 등장하면서 관련있는 기능들을 분류해서 관리해야한다.
패키지 = 폴더, 디렉토리
<pack.Data.java>
package pack;
public class Data {
public Data(){
System.out.println("패키지 pack Data 생성");
}
}
<pack.a.User.java>
package pack.a;
public class User {
public User(){
System.out.println("패키지 pack.a 회원 생성");
}
}
참고 : 생성자에 public 사용 -> 다른 패키지에서 이 클래스의 생성자를 호출하려면 public을 사용해야한다.
<PackageMain1.java>
package pack;
public class PackageMain1 {
public static void main(String[] args) {
Data data = new Data();
pack.a.User user = new pack.a.User();
}
}
<결과>
<PackageMain2.java>
package pack;
import pack.a.User;
public class PackageMain2 {
public static void main(String[] args) {
Data data = new Data();
User user = new User(); //import 사용으로 패키지명 생략가능
}
}
package pack;
//import pack.a.User;
import pack.a.*; //pack.a의 모든 클래스 사용
public class PackageMain2 {
public static void main(String[] args) {
Data data = new Data();
User user = new User(); //import 사용으로 패키지명 생략가능
}
}
<pack.b.User.java>
package pack.b;
public class User {
public User() {
System.out.println("패키지 pack.b 회원 생성");
}
}
<PackageMain3.java>
package pack;
//import pack.a.User;
import pack.a.User;
public class PackageMain3 {
public static void main(String[] args) {
User userA = new User(); //import 사용으로 패키지명 생략가능
pack.b.User userB = new pack.b.User();
}
}
=> 같은 이름의 클래스가 있다면 import는 둘중 하나만 선택할 수 있다. 이때는 자주 사용하는 클래스를 import 하고 나머지를 패키지를 포함한 전체 경로를 적어준다. 물론 둘다 전체 경로를 적어준다면 import 는 사용하지 않아도 됨
a , a.b , a.c 이렇게 3개의 패키지가 존재한다.
패키지가 계층 구조를 이루더라도 모든 패키지는 서로 다른 패키지이다.
전체구조도
<com.helloshop.user.User.java>
package com.helloshop.user;
public class User {
String userId;
String name;
}
<com.helloshop.user.UserService.java>
package com.helloshop.user;
public class UserService {
}
<com.helloshop.product.Product.java>
package com.helloshop.product;
public class Product {
String productId;
int price;
}
<com.helloshop.product.ProductService.java>
package com.helloshop.product;
public class ProductService {
}
<com.helloshop.order.Order.java>
package com.helloshop.order;
import com.helloshop.product.Product;
import com.helloshop.user.User;
public class Order {
User user;
Product product;
public Order(User user, Product product){
this.user = user;
this.product=product;
}
}
=> public Order(User user, Product product)에서 public : 접근제어자
-> public이 있어야 다른 패키지에서 order를 호출할 수 있다.
<com.helloshop.order.OrderService.java>
package com.helloshop.order;
import com.helloshop.product.Product;
import com.helloshop.user.User;
public class OrderService {
public void order(){
User user =new User();
Product product = new Product();
Order order = new Order(user, product);
}
}
자바는 public, private 같은 접근 제어자(access modifier) 를 제공한다.
접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드에 접근하는것을 허용하거나 제항 할 수 있다.
<Speaker.java>
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("음량을 10 감소합니다.");
}
void showVolume(){
System.out.println("현재 음량: "+ volume);
}
}
<SpeakerMain.java>
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();
}
}
<결과>
소리를 더 올리고 싶은 새로운 개발자는 max 를 200으로 올렸다가 제품이 고장나게 된다.
volume 필드 직접 접근해서 수정 했다
<SpeakerMain.java>
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();
}
}
<결과>
why?
Speaker 객체를 사용하는 사용자는 Speaker 의 volume 필드와 메서드에 모두 접근할 수 있다.
-> 앞서 volumeUp() 과 같은 메서드를 만들어서 음량이 100을 넘지 못하도록 기능을 개발했지만 소용이 없다. 왜냐하면 Speaker 를 사용하는 입장에서는 volume 필드에 직접 접근해서 원하는 값을 설정할 수 있기 때문이다.
=> volume 필드의 외부 접근을 막아야한다.
<Speaker.java>
package access;
public class Speaker {
private int volume; //private 사용
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("음량을 10 감소합니다.");
}
void showVolume(){
System.out.println("현재 음량: "+ volume);
}
}
=> private 접근 제어자는 모든 외부 호출을 막는다. 따라서 private 이 붙은 경우 해당 클래스 내부에서만 호출할 수 있다.
좋은 프로그램은 무한한 자유도가 주어지는 프로그램이 아니라 적절한 제약을 제공하는 프로그램이다.
private : 모든 외부 호출을 막는다.
default (package-private): 같은 패키지안에서 호출은 허용한다.
protected : 같은 패키지안에서 호출은 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.
public : 모든 외부 호출을 허용한다
순서대로 private 이 가장 많이 차단하고, public 이 가장 많이 허용한다.
private -> default -> protected -> public
접근 제어자는 필드와 메서드, 생성자에 사용된다. 추가로 클래스 레벨에도 일부 접근 제어자를 사용할 수 있다..
public class Speaker { //클래스 레벨
private int volume; //필드
public Speaker(int volume) {} //생성자
public void volumeUp() {} //메서드
public void volumeDown() {}
public void showVolume() {}
}
<access.a.AccessData.java>
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);
}
void privateMethod(){
System.out.println("privateMethod 호출 "+ privateField);
}
public void innerAccess(){
System.out.println("내부 호출");
publicField = 100;
defaultField = 200;
privateField = 300;
publicMethod();
defaultMethod();
privateMethod();
}
}
=> innerAccess()는 내부호출 메서드. 내부호출은 자기자신에게 접근하라는 것 = private을 포함한 모든 곳에 접근 가능
<access.a.AccessInnerMain.java>
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();
}
}
=> public : 모든 접근 허용
default : 같은 패키지만 허용
<결과>
<access.a.AccessOuterMain.java>
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();
data.innerAccess();
}
}
<결과>
<access.a.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{
}
<access.a.PublicClassInnerMain.java>
package access.a;
public class PublicClassInnerMain {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
DefaultClass1 class1 = new DefaultClass1();
DefaultClass2 class2 = new DefaultClass2();
}
}
<access.b.PublicClassOuterMain.java>
package access.b;
//import access.a.DefaultClass1;
//import access.a.DefaultClass2;
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();
}
}
캡슐화(Encapsulation)는 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어서 외부에서의 접근을 제한하는 것을 말한다. 캡슐화를 통해 데이터의 직접적인 변경을 방지하거나 제한할 수 있다. 캡술화를 안전하게 완성할 수 있게 해주는 장치가 접근제어자 이다.
쉽게 말해서 속성과 기능을 하나로 묶고, 외부에 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것
왠만하면 멤버변수들은 private 으로 막아둔다.
기능 중 외부에서 사용하지 않고 내부에서만 사용하는 기능들은 숨기는 것이 좋다.
<BankAccount.java>
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 void withdraw(int amount){
if(isAmountValid(amount) && balance -amount >=0){
balance -= amount;
}else{
System.out.println("유효하지 않은 금액이거나 잔액이 부족합니다.");
}
}
public int getBalance(){
return balance;
}
private boolean isAmountValid(int amount){
return amount>0;
}
}
<BankAccountMain.java>
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());
}
}
isAmountValid 가 public 이라면 개발자는 해당 메서드를 사용해서 검증해야하는지 의문을 가질 수가 있다.
<MaxCounter.java>
package access.ex;
public class MaxCounter {
private int count;
public int max;
public MaxCounter(int max){
this.max = max;
}
public void increment(){
//검증로직
if(count>=max){
System.out.println("최대값을 초과할 수 없습니다.");
return; //검즈에서 통과 못하면 실행으로 넘어가지 못함, else 를 사용하지 않아도됨
}
//실행 로직
count++;
}
public int getCount(){
return count;
}
}
<CounterMain.java>
package access.ex;
public class CounterMain {
public static void main(String[] args) {
MaxCounter maxCounter = new MaxCounter(3);
maxCounter.increment();
maxCounter.increment();
maxCounter.increment();
maxCounter.increment();
int count = maxCounter.getCount();
System.out.println(count);
}
}
<Item.java>
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 getTotalProce(){
return price*quantity;
}
}
<ShoppingCart.java>
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;
}
items[itemCount] = item;
itemCount++;
}
public void displayItems(){
System.out.println("장바구니 상품 출력");
for (int i=0; i<itemCount; i++){
Item item = items[i];
System.out.println("상품명: "+ item.getName()+" 합계: "+ item.getTotalProce());
}
System.out.println("전체 가격 합 : " + calTotalPrice());
}
private int calTotalPrice(){
int totalPrice = 0;
for (int i=0; i<itemCount; i++){
Item item = items[i];
totalPrice += item.getTotalProce();
}
return totalPrice;
}
}
<ShoppingCartMain.java>
package access.ex;
public class ShoppingCartMain {
public static void main(String[] args) {
ShoppingCart shoppingCart = new ShoppingCart();
Item item1 = new Item("사과",1000,2);
Item item2 = new Item("바나나",500,3);
Item item3 = new Item("망고",3000,2);
shoppingCart.addItem(item1);
shoppingCart.addItem(item2);
shoppingCart.addItem(item3);
shoppingCart.displayItems();
}
}
<결과>