6 분 소요



[김영한의 실전 자바 - 기본편↗️]을 듣고 정리한 글입니다.



1. 접근 제어자

1.1 접근 제어자가 필요한 이유

자바에서 제공하는 publicprivate 같은 접근 제어자는 클래스의 필드나 메서드에 대한 접근을 제한하는 역할을 한다.

스피커 예제에서 볼 수 있듯이, 외부에서 volume 필드에 직접 접근할 수 있도록 허용하면 음량이 100을 초과할 수 있고, 이는 스피커의 부품에 손상을 줄 수 있다.


잘못된 코드 예: 접근 제어자가 없는 경우

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

실행결과

현재 음량: 90
음량을 10 증가합니다.
현재 음량: 100
음량을 증가할 수 없습니다. 최대 음량입니다.
현재 음량: 100
volume 필드 직접 접근 수정
현재 음량: 200
  • 위 코드에서 volume 필드는 접근 제어자가 없기 때문에 외부에서 직접 수정할 수 있다.
  • 이는 volume 값을 임의로 200과 같은 잘못된 값으로 설정할 수 있게 만들어 시스템을 고장 낼 수 있다.


1.2 private 접근 제어자로 문제 해결

이 문제를 해결하려면 volume 필드를 private으로 설정하여 외부에서 직접 접근할 수 없도록 막아야 한다.

이를 통해 개발자가 설정한 규칙(예: 음량은 100을 넘지 않음)을 보장할 수 있다.

package access;

public class Speaker {
    private int volume;  // 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("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();

        // 필드에 직접 접근을 시도 (오류 발생)
        // speaker.volume = 200;  // 컴파일 오류 발생
        speaker.showVolume();
    }
}

실행 결과

현재 음량: 90
음량을 10 증가합니다.
현재 음량: 100
음량을 증가할 수 없습니다. 최대 음량입니다.
현재 음량: 100
  • private 접근 제어자로 인해, volume 필드에 외부에서 접근할 수 없게 되었다.
  • 이제 volume 필드는 스피커 클래스 내부에서만 수정될 수 있으며, volumeUp() 메서드를 통해서만 음량을 조절할 수 있게 되었다.



2. 접근 제어자의 종류와 역할

2.1 접근 제어자의 종류

자바에는 다양한 접근 제어자가 있으며, 각각의 접근 수준은 다음과 같다.

  • private: 해당 클래스 내에서만 접근 가능.
  • default (접근 제어자를 지정하지 않은 경우): 같은 패키지 내에서만 접근 가능.
  • protected: 같은 패키지 또는 상속받은 클래스에서 접근 가능.
  • public: 모든 클래스에서 접근 가능.

➡️ 즉, 중요한 필드나 메서드는 private으로 선언하여 클래스 외부에서 직접 접근하지 못하도록 하고, public 메서드를 통해서만 필드에 접근하도록 설계해야 한다.


2.2 접근 제어자의 사용 위치

클래스 레벨

  • public, default만 사용할 수 있다.
  • privateprotected는 클래스 선언에 사용할 수 없다.


필드, 메서드, 생성자

  • 모든 접근 제어자(private, default, protected, public)를 사용할 수 있다.
  • 필드나 메서드의 접근을 제어하여 외부 클래스나 상속받은 클래스에서 접근을 제한하거나 허용할 수 있다.


2.3 예제 코드로 접근 제어자 확인

패키지: access.a

package access.a;

public class AccessData {
    public int publicField;   // public 필드
    int defaultField;         // default 필드
    private int privateField; // private 필드

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

        // innerAccess 메서드로 private 필드와 메서드 접근 가능
        data.innerAccess();
    }
}

실행 결과

publicMethod 호출 1
defaultMethod 호출 2
내부 호출
publicMethod 호출 100
defaultMethod 호출 200
privateMethod 호출 300


패키지: access.b

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

        // public 메서드인 innerAccess는 호출 가능
        data.innerAccess();
    }
}

실행 결과

publicMethod 호출 1
내부 호출
publicMethod 호출 100
defaultMethod 호출 200
privateMethod 호출 300



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

클래스 레벨에서는 public, default 두 가지 접근 제어자를 사용할 수 있다.

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

  • public
    • 클래스가 public 접근 제어자를 가지면, 어디서든 접근이 가능하다.
    • 이 클래스는 반드시 파일 이름과 동일해야 하며, 하나의 자바 파일에는 public 클래스가 하나만 있어야 한다.
  • default (package-private)
    • 접근 제어자를 명시하지 않으면 default가 적용된다.
    • default 클래스는 같은 패키지 내에서만 접근이 가능하며, 다른 패키지에서는 접근할 수 없다.
  • private 및 protected
    • 클래스 레벨에서 사용할 수 없다. (단, 내부 클래스의 경우 private와 protected 사용 가능)


3.2 클래스 레벨 접근 제어자 예시

public 클래스와 default 클래스의 예

  • PublicClasspublic 접근 제어자를 가지고 있기 때문에 외부 패키지에서도 접근할 수 있다.
  • DefaultClass1DefaultClass2default 접근 제어자이므로 같은 패키지 내에서만 접근이 가능하다.
// 파일명: 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 {
}


3.3 외부에서 클래스 접근하기

같은 패키지에 있는 PublicClassInnerMain에서는 default 클래스에 접근이 가능하다.

package access.a;

public class PublicClassInnerMain {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        DefaultClass1 class1 = new DefaultClass1();
        DefaultClass2 class2 = new DefaultClass2();
    }
}


그러나 다른 패키지에 있는 PublicClassOuterMain에서는 default 클래스에 접근할 수 없다.

package access.b;

import access.a.PublicClass;

public class PublicClassOuterMain {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        // 다른 패키지에서는 default 클래스에 접근 불가
        // DefaultClass1 class1 = new DefaultClass1();  // 오류
        // DefaultClass2 class2 = new DefaultClass2();  // 오류
    }
}



4. 캡슐화

4.1 캡슐화 개념

캡슐화는 객체 지향 프로그래밍에서 중요한 개념이다. 데이터와 그 데이터를 처리하는 메서드를 하나의 객체로 묶고, 외부에서 불필요한 접근을 제한하여 객체의 무결성을 유지하는 것을 말한다.

캡슐화를 통해 객체의 내부 상태를 보호하고, 외부에서의 무분별한 변경을 방지할 수 있다.


4.2 캡슐화의 두 가지 핵심 원칙

  • 데이터를 숨겨라
    • 객체의 데이터를 직접 노출하지 않고, 메서드를 통해서만 접근하도록 한다.
    • 이를 통해 잘못된 데이터 접근이나 수정으로 인한 오류를 방지할 수 있다.
  • 기능을 숨겨라
    • 외부에서 불필요하게 알 필요가 없는 기능은 숨기고, 꼭 필요한 기능만 노출한다.
    • 이를 통해 객체의 복잡도를 낮추고, 사용자가 객체를 쉽게 사용할 수 있게 한다.


4.3 캡슐화 예시

다음은 잘 캡슐화된 BankAccount 클래스이다.

  • balance 필드는 private로 선언되어 외부에서 직접 접근할 수 없다.
  • deposit(), withdraw(), getBalance() 메서드는 public으로 선언되어 외부에서 사용할 수 있는 기능이다.
  • isAmountValid() 메서드는 외부에서 사용할 필요가 없는 내부적인 검증 로직이므로 private로 선언되어 외부에서 호출할 수 없다.
package access;

public class BankAccount {
    private int balance;

    public BankAccount() {
        balance = 0;
    }

    // public 메서드: 입금
    public void deposit(int amount) {
        if (isAmountValid(amount)) {
            balance += amount;
        } else {
            System.out.println("유효하지 않은 금액입니다.");
        }
    }

    // public 메서드: 출금
    public void withdraw(int amount) {
        if (isAmountValid(amount) && balance - amount >= 0) {
            balance -= amount;
        } else {
            System.out.println("유효하지 않은 금액이거나 잔액이 부족합니다.");
        }
    }

    // public 메서드: 잔고 조회
    public int getBalance() {
        return balance;
    }

    // private 메서드: 금액 유효성 검사
    private boolean isAmountValid(int amount) {
        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());
    }
}





5.






카테고리:

업데이트:

댓글남기기