👩‍💻 BackEnd/☕️ 자바 [Java]

[Java] 자바의 정석 : 접근제어자, 캡슐화

minhe2810 2024. 2. 23. 13:53

접근 제어자의 종류는 빠삭하게 알지만 그 활용이 잘 이해가 안간다. 

 

접근 제어자의 종류 

  • public : 접근 제한이 거의 없음. 
  • (default) : 같은 패키지 내에서 접근 가능. (접근 제어자 명시를 하지 않으면 default 접근 제어자) 
  • protected  : 같은 패키지 내에서 그리고 상속받은 자손 클래스에서 접근이 가능.
  • private : 같은 클래스 내에서만 접근이 가능. 
학습 목표 : 오늘 예시를 통해서 조금 더 확실하게 익히고, 어떤 상황에서 접근 제어자를 활용할 수 있는지 알아보자. 

 

package pkg1;

class MyParent {

    public int pub;
    protected int ptd;
    int dft;
    private int prt;

    public void printMembers() {
        System.out.println(pub);
        System.out.println(ptd);
        System.out.println(dft);
        System.out.println(prt);
    }
}

public class MyParentTest {
    public static void main(String[] args) {
        MyParent mp = new MyParent();
        System.out.println(mp.pub);
        System.out.println(mp.ptd);
        System.out.println(mp.dft);
//        System.out.println(mp.prt);   // 에러 : 같은 클래스가 아니므로 에러 발생

    }
}

 

이렇게 MyParent 클래스를 default class 로 선언하고

멤버 변수들을 각각 다른 접근 제어자로 설정을 해보자. 

 

그리고 같은 클래스에서 각각의 멤버 변수를 메서드로 호출을 해보자. 

모두 정상적으로 호출된다. 왜냐하면 접근 가능 범위가 가장 좁은 private는 같은 클래스에서도 접근이 가능하기 때문이다. 

 

그럼 이제 MyParentTest 라는 클래스를 public class 로 선언하고 

MyParent 객체를 생성 후 각각의 멤버 변수를 호출해보자.

그러자 private 접근 제어자를 가진 prt 멤버 변수만 호출이 되지 않는 것을 발견할 수 있다. 

왜냐하면 private 는 같은 클래스 내에서만 호출이 가능한데 지금은 다른 클래스를 만들고 나서 그 안에서 호출했기 때문이다. 

 

package pkg2;

import pkg1.MyParent;

class MyChild extends MyParent {

    public void printMembers() {
        System.out.println(pub);
        System.out.println(ptd);
        System.out.println(dft); // 에러 : 다른 패키지에서 접근 불가능
        System.out.println(prt); // 에러 : 다른 클래스, 패키지에서 접근 불가능
    }
}

 

다른 패키지를 생성한 뒤 MyParent 클래스를 상속 받는 클래스를 선언한다. 

그리고 다시 MyParent 함수들을 프린트 하는 메서드를 작성하여 실행시켜본다. 

 

그러면 default, private 접근 제어자를 가진 멤버 변수에서 에러가 난다. 왜냐하면 default는 다른 패키지에서 접근이 불가능하고, 

private는 다른 클래스, 다른 패키지에서 접근이 불가능하기 때문이다. 

또한 protected 를 접근 제어자로 가진 멤버 변수는 다른 패키지이지만 MyParent 를 상속 받았기에 해당 멤버 변수를 호출할 수 있다. 

 

같은 패키지에 다른 클래스를 정의하고, 

다시 MyParent 객체를 생성한 뒤 멤버 변수를 호출해보면 다음과 같다. 

역시 상속 관계가 아닌 경우에는 protected 까지 에러가 나는 것을 확인할 수 있다 (호출불가능).

public class MyParentTest2 {

    public static void main(String[] args) {

        MyParent mp = new MyParent();
        System.out.println(mp.pub);
//        System.out.println(mp.ptd);   // 에러 
//        System.out.println(mp.dft);   // 에러 
//        System.out.println(mp.prt);   // 에러 : 같은 클래스가 아니므로 에러 발생
    }

 

캡슐화와 접근 제어자 

캡슐화 : 직접 접근을 막고 간접 접근을 허용하는 것을 캡슐화라고 한다. 

접근 제어자를 사용하는 이유 ? 

  • 외부로부터 데이터를 보호하기 위해 즉, 데이터가 엉뚱한 값으로 바뀌는 것을 막을 수 있다. 
  • 메서드를 통해서만 접근이 가능할 수 있도록 할 수 있다.  -> 우선 이 부분에 집중해보자 
  • 외부적으로 불필요한, 내부적으로만 사용되는 부분을 감추기 위해서 
package pkg2;

public class Time {

    private int hour;
    private int min;
    private int sec;


    public int getHour() {
        return hour;
    }

    public void setHour(int hour) {
        if (hour < 0 || hour > 23) {
                return ;
        }
        this.hour = hour;
    }
}

 

예를 들어 시간 클래스를 만들었다.

시간은 정해진 범위가 명확하고 이 범위를 벗어나서 표현하면 안된다는 규칙이 있다. 

이 경우 다른 클래스에서 시간 클래스에 시간 범위가 아닌 hour 에 34를 넣으려고 하면 이를 막아야 한다. 

 

따라서 이상한 데이터가 주입되는 것을 막기 위해서 즉, 데이터를 외부로 부터 보호하기 위해서 

접근 제어자를 사용하는 것이다. 따라서 멤버 변수의 접근 제어자를 private로 선언한 뒤

메서드를 public 으로 설정하여 메서드로 변수에 접근할 수 있도록 하는 것이다. 

 

getter, setter 개념이 여기서 나오는 것 같다. 

 

그래서 값을 꺼낼 때는 getter 

값을 주입할 때는  setter 를 사용하는데 setter에 값의 범위를 지정해줘서 

hour 가 0이하 이거나 23 이상일 때는 return 을 해주고, 아닐 때는 hour 의 값을 매개변수로 들어온 값으로 저장을 해준다. 

 

이렇게 하면 데이터의 값을 엉뚱한 값으로 바꾸는 것을 막을 수 있다. 

 


 

추가적으로 setter 에서 유효한 값인지 확인하는 부분을 따로 메서드로 추출해놓을 수 있다. 

간단한 코드에서는 굳이...지만 복잡한 코드에서는 메서드를 따로 추출해놓는게 좋다고 한다. 

 

다음의 코드를 보자 

    public void setHour(int hour) {
        if (hour < 0 || hour > 23) {
                return ;
        }
        this.hour = hour;
    }

 

여기서 

public void setHour(int hour) {
    if (isaBoolean(hour)) {
            return ;
    }
    this.hour = hour;
}

// 매개변수로 넘겨진 hour 가 유효한지 확인하는 메서드 : private 로 선언 밖에서 사용하지 않는 메서드이기 때문
private static boolean isaBoolean(int hour) {
    return hour < 0 || hour > 23;
}

이런식으로 isaBoolean 이라는 메서드로 따로 추출을 해둔다. 

인텔리제이에서 if문 ()안에 있는 부분을 드래그 한 뒤 cmd + option + M (mac os 기준) 를 누르면 메서드 추출이 가능하다. 

 

인텔리제이가 알아서 메서드를 추출해주는데 이때 만들어진 메서드의 접근 제어자를 보면 private 인 것을 확인 할 수 있다. 

이런 유효성을 확인하는 메서드의 경우에는 해당 클래스 내부에서만 사용되는 검증 로직이기 때문에 굳이 public 으로 선언할 필요가 없다. 

 

남궁성 개발자님의 말에 따르면 

접근제어자는 최대한 좁게 설정을 해놓고 필요시 넓히는 것이 바람직하다고 한다. 

 

추가적으로 접근 제어자를 신경 써야하는 이유 중 하나는

만약에 이 코드를 테스트를 해야하는 상황이 온다면 메서드의 접근 제어자를 보고 이 클래스에서만 사용하는 코드인지 아닌지를 판단해서 테스트를 조금 더 효율적으로 할 수 있기 때문이라고 한다.