코린이의 소소한 공부노트

제어자 본문

Java

제어자

무지맘 2022. 4. 16. 17:28

영어에는 여러 품사가 있는데, 그중 형용사는 명사 앞 또는 뒤에 붙어서 명사를 수식해주는 역할을 한다.

갑자기 영어 시간이 된...것은 아니고, 이번에 공부할 제어자가 형용사 역할을 하기 때문에 잠깐 언급해본 것뿐이다..

 

제어자

1. 클래스와 클래스의 멤버(변수, 메서드)에 부가적인 의미를 부여해준다.

  - 예시) public: '공공의, 공적인'이라는 뜻으로, 이것을 붙인 클래스나 변수, 메서드는 누구나 사용할 수 있는 상태가 됨

  - 나머지는 아래에서 자세히 설명 예정

2. 접근 제어자와 그 외 제어자로 나눌 수 있다.

  - 접근 제어자: public, protected, default(아무것도 붙이지 않으면 됨), private

  - 그 외 제어자: static, final, abstract, native, transient, synchronized, volatile, strictfp

3. 제어자는 클래스나 변수, 메서드 앞에 붙여 사용한다.

  - 하나의 대상에 여러 제어자를 같이 사용할 수 있음(접근 제어자는 4개 중 1개만 사용 가능)

  - 순서는 상관없지만, 보통 접근 제어자를 제일 앞에 씀

public static final int MAX = 100; // 접근 제어자가 맨 앞
static final public int MIN = 0; // 접근 제어자가 맨 뒤

 

먼저 볼 것은 일반 제어자인데, 가장 많이 쓰는 3가지만 살펴보려 한다.

 

[static - 클래스의, 공통적인]

1. static + 멤버 변수

  - 모든 인스턴스에 공통적으로 사용되는 클래스 변수(cv)가 된다.

  - cv는 인스턴스를 생성하지 않고도 사용 가능하다.

  - cv는 클래스가 메모리에 로드될 때 생성된다.

2. static + 메서드

  - 인스턴스를 생성하지 않고도 호출이 가능한 스태틱 메서드(sm)가 된다. = 클래스 메서드

  - sm 내에서는 iv, im을 직접 사용할 수 없다.

class StaticClass{
    static int i = 100; // 클래스 변수
    
    static {  // 클래스 초기화 블럭
        // static 변수의 복잡한 초기화
    }

    static int max(int a, int b) { // 클래스 메서드
        return a>b ? a : b;
    }
}

 

[final - 마지막의, 변경될 수 없는]

1. final + 클래스

  - 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다.

  - 그렇기 때문에 다른 클래스의 조상이 될 수 없다. -> 마지막 클래스

  - 예시) String 클래스, Math 클래스

2. final + 메서드

  - 변경될 수 없는 메서드가 된다.

  - 그렇기 때문에 오버라이딩을 통해 재정의 될 수 없다.

3. final + 변수

  - 값을 변경할 수 없는 상수가 된다.

  - 보통 변수 이름을 대문자로 표기한다.

final class FinalClass{ // 조상이 될 수 없는 클래스
    final int LIMIT = 200; // 값을 변경할 수 없는 멤버변수(상수)

    final int getLimit() { // 오버라이딩할 수 없는 메서드
        final int LM = LIMIT; // 값을 변경할 수 없는 지역변수(상수)
        return LM;
    }
}

 

[abstract - 추상의, 미완성의]

1. abstract + 메서드

  - 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알려준다.

2. abstract + 클래스

  - 클래스 내에 추상 메서드가 선언되어있음을 의미한다.

  - 클래스 내의 메서드가 미완성이므로 클래스도 미완성이라는 뜻이다.

  - 미완성 설계도이기 때문에 객체 생성을 할 수 없다.

    -> 추상 클래스를 상속받아서 완전한 클래스(구상 클래스)로 만들어야 객체 생성이 가능해진다.

abstract class AbstractClass{ // 추상 클래스(추상 메서드를 포함한 클래스)
    abstract void doSomething(); // 추상 메서드(구현부가 없는 메서드)
}

// 메인 내부
AbstractClass a = new AbstractClass(); // error. Cannot instantiate the type AbstractClass

 

지금부터 볼 접근 제어자는 이름 그대로 해당 클래스/메서드/변수를 사용할 수 있는 범위를 제어하는 역할을 한다. 접근 제어자의 영어 뜻과 함께 접근 가능 범위를 여러 각도에서 설명해보겠다.

1. private: '사적인, 은밀한'

  - 같은 클래스 내에서만 접근이 가능하다.

  - private로 선언된 것의 유효 범위는 해당 클래스 내부이다.

  - 다른 클래스에서는 접근할 수 없다.

2. default: '기본' (-> 접근 제어자를 아무것도 붙이지 않은 것)

  - 같은 패키지 내에서만 접근이 가능하다.

  - 접근 제어자를 붙이지 않으면 그것의 유효 범위는 해당 패키지 내부이다.

  - 다른 패키지에서는 접근할 수 없다.

3. protected

  - 같은 패키지 내에서, 그리고 다른 패키지의 자손 클래스에서 접근이 가능하다.

  - protected로 선언된 것의 유효 범위는 해당 패키지 내부 + 다른 패키지에 선언된 자손 클래스 내부이다.

  - 다른 패키지에 있으면서 상속받지 않은 클래스에서는 접근할 수 없다.

4. public

  - 접근 제한이 없다.

  - public으로 선언된 것의 유효 범위는 프로젝트 전체다.

  - 모든 패키지에서 접근 가능하다.

 

아래 코드를 읽고 그림으로 설명된 접근 제어자의 범위를 보면 애매하게 이해된 부분도 명확하게 알 수 있게 된다! 코드가 조금 길기 때문에 천천히 읽어보는 것을 추천한다. 범위를 정하는 기준점이 되는 클래스는 Parent 클래스이다.

// Parent.java
package pkg1;

public class Parent{ // 접근 제어자가 default가 아닌 이유는 설명 끝부분에
    private   int pvt; // 같은 클래스
              int dft; // 같은 패키지
    protected int prt; // 같은 패키지 + 다른 패키지 자손
    public    int pub; // 전체

    public void printMembers() {
        System.out.println(pvt); // OK
        System.out.println(dft); // OK
        System.out.println(prt); // OK
        System.out.println(pub); // OK
    }
}

1. private의 범위 확인 - pkg1 패키지에 Parent라는 public 클래스를 만들었다.

  1) 접근 제어자 별로 정수형 변수를 만들었다.

  2) public 메서드도 1개 만들었다. 이 메서드는 4개의 변수를 다 출력하는 역할을 한다.

    - 같은 클래스 내부이기 때문에 pvt에 접근 가능 -> OK

    - 같은 클래스 -> 같은 패키지(pkg1) 내부이기 때문에 dft에 접근 가능 -> OK

    - 같은 패키지 내부이기 때문에 prt에 접근 가능 -> OK

    - public은 접근 제한이 없으므로 pub에 접근 가능 -> OK

// Parent.java
package pkg1;

class Test1 {
    public static void main(String[] args) {
        Parent p = new Parent();
        System.out.println(p.pvt); // error
        System.out.println(p.dft); // OK
        System.out.println(p.prt); // OK
        System.out.println(p.pub); // OK
    }
}

2. default의 범위 확인 - pkg1 패키지에 Test1라는 default 클래스를 만들었다.

  1) 메인에서 Parent 객체 p를 생성했다.

  2) p를 이용해 4가지 변수에 접근해보기로 했다.

    - Parent와 다른 클래스이기 때문에 pvt에 접근 불가 -> error

    - 같은 패키지(pkg1) 내부이기 때문에 dft에 접근 가능 -> OK

    - 같은 패키지 내부이기 때문에 prt에 접근 가능 -> OK

    - public은 접근 제한이 없으므로 pub에 접근 가능 -> OK

// Test2.java
package pkg2;

import pkg1.Parent; // 상속을 위한 import

class Child extends Parent{
    public void printMembers() {
        System.out.println(pvt); // error
        System.out.println(dft); // error
        System.out.println(prt); // OK
        System.out.println(pub); // OK
    }	
}

3. protected의 범위 확인 - pkg2 패키지에 Child라는 default 클래스를 만들었다.

  1) Parent 클래스를 상속받았기 때문에, Parent 클래스의 모든 멤버를 이용할 수 있다.

  2) 접근 가능 여부를 확인해보기 위해 Parent 클래스에 있던 메서드를 똑같이 써봤다.

    - Parent와 다른 클래스이기 때문에 pvt에 접근 불가 -> error

    - 다른 패키지(pkg1 != pkg2) 내부이기 때문에 dft에 접근 불가 -> error

    - 다른 패키지(pkg1 != pkg2)지만 pkg1의 클래스(Parent)를 상속받은 자손이기 때문에 prt에 접근 가능 -> OK

    - public은 접근 제한이 없으므로 pub에 접근 가능 -> OK

// Test2.java
package pkg2;

import pkg1.Parent; // 객체 생성을 위한 import

public class Test2 {
    public static void main(String[] args) {
        Parent p = new Parent();
        System.out.println(p.pvt); // error
        System.out.println(p.dft); // error
        System.out.println(p.prt); // error
        System.out.println(p.pub); // OK
    }
}

4. public의 범위 확인 - pkg2 패키지에 Test2라는 public 클래스를 만들었다.

  1) Parent 클래스를 import하고 메인에서 p라는 객체를 만들었다.

  2) p를 이용해 4가지 변수에 접근해보기로 했다.

    - Parent와 다른 클래스이기 때문에 pvt에 접근 불가 -> error

    - 다른 패키지(pkg1 != pkg2) 내부이기 때문에 dft에 접근 불가 -> error

    - 다른 패키지(pkg1 != pkg2) + pkg1의 클래스를 상속받지 않았기 때문에 prt에 접근 불가 -> error

    - public은 접근 제한이 없으므로 pub에 접근 가능 -> OK

 

 

위 코드와 설명을 읽고, 색깔로 구분된 접근 제어자별 범위를 살펴보면 충분히 이해가 될 것이다.

 

[파일 TMI]

1. Child 클래스가 상속받기 위해서는 다른 패키지에 있는 Parent 클래스가 public으로 선언되어야 한다.

2. Parent 클래스와 같은 패키지 내의 다른 클래스가 있어야 defualt의 범위를 확인할 수 있다.

     -> 그래서 메인은 Test1 클래스에 있지만 pkg1이 있는 파일 이름이 Parent 클래스가 되었다.

3. Child 클래스는 protected의 범위, Test2 클래스는 public의 범위를 확인하기 위해 만든 클래스이다.

'Java' 카테고리의 다른 글

다형성  (0) 2022.04.19
접근 제어자를 이용한 캡슐화  (0) 2022.04.18
임포트와 스태틱 임포트  (0) 2022.04.14
패키지와 클래스 경로  (0) 2022.04.13
생성자 super() vs 참조변수 super  (0) 2022.03.23