2.5~6.Java 패키지, 접근제어자

sein lee·2024년 5월 13일
0

java-study

목록 보기
12/12
post-custom-banner

패키지

패키지 시작

기능이 점점 추가되어서 프로그램이 아주 커진다면?
-> 매우 많은 클래스가 등장하면서 관련있는 기능들을 분류해서 관리해야한다.

패키지 = 폴더, 디렉토리

패키지 사용

<pack.Data.java>

package pack;

public class Data {
    public Data(){
        System.out.println("패키지 pack Data 생성");
    }
}
  • 패키지 사용 시 항상 코드 첫줄에 package packageName을 작성

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

    }
}

<결과>

import

<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, 다음줄에는 import 사용
  • 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 는 사용하지 않아도 됨

패키지 규칙

  • 패키지의 이름과 위치는 폴더(디렉토리) 위치와 같아야 한다. (필수)
  • 패키지 이름은 모두 소문자를 사용한다. (관례)
  • 패키지 이름의 앞 부분에는 일반적으로 회사의 도메인 이름을 거꾸로 사용한다. 예를 들어,
    com.company.myapp 과 같이 사용한다. (관례)

패키지와 계층구조

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

package-private

  • 접근 제어자를 명시하지 않으면 같은 패키지 안에서 호출을 허용하는 default 접근 제어자가 적용
  • 동일한 패키지 내의 다른 클래스에서만 접근이 가능

접근제어자 사용위치

접근 제어자는 필드와 메서드, 생성자에 사용된다. 추가로 클래스 레벨에도 일부 접근 제어자를 사용할 수 있다..

예시

public class Speaker { //클래스 레벨
 private int volume; //필드
 public Speaker(int volume) {} //생성자
 public void volumeUp() {} //메서드
 public void volumeDown() {}
 public void showVolume() {}
}

접근 제어자의 핵심은 속성과 기능을 외부로부터 숨기는 것

  • private 은 나의 클래스 안으로 속성과 기능을 숨길 때 사용, 외부 클래스에서 해당 기능을 호출할 수 없다.
  • default 는 나의 패키지 안으로 속성과 기능을 숨길 때 사용, 외부 패키지에서 해당 기능을 호출할 수 없다.
  • protected 는 상속 관계로 속성과 기능을 숨길 때 사용, 상속 관계가 아닌 곳에서 해당 기능을 호출할 수 없다.
  • public 은 기능을 숨기지 않고 어디서든 호출할 수 있게 공개한다.

접근제어자 사용 - 필드,메서드

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

<결과>

접근제어자 사용 - 클래스레벨

클래스 레벨의 접근 제어자 규칙

  • 클래스 레벨의 접근 제어자는 public , default 만 사용할 수 있다.
    private , protected 는 사용할 수 없다.
  • public 클래스는 반드시 파일명과 이름이 같아야 한다.
    하나의 자바 파일에 public 클래스는 하나만 등장할 수 있다.
    하나의 자바 파일에 default 접근 제어자를 사용하는 클래스는 무한정 만들 수 있다.

<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)는 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶어서 외부에서의 접근을 제한하는 것을 말한다. 캡슐화를 통해 데이터의 직접적인 변경을 방지하거나 제한할 수 있다. 캡술화를 안전하게 완성할 수 있게 해주는 장치가 접근제어자 이다.

쉽게 말해서 속성과 기능을 하나로 묶고, 외부에 필요한 기능만 노출하고 나머지는 모두 내부로 숨기는 것

1. 데이터(속성) 숨기기

왠만하면 멤버변수들은 private 으로 막아둔다.

2. 기능 숨기기

기능 중 외부에서 사용하지 않고 내부에서만 사용하는 기능들은 숨기는 것이 좋다.

캡슐화 예시

<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 이라면 개발자는 해당 메서드를 사용해서 검증해야하는지 의문을 가질 수가 있다.

문제와 풀이1

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

문제와 풀이2

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

<결과>

profile
개발감자
post-custom-banner

0개의 댓글