[JAVA] 자바 제어자 (접근 지정자, static 제어자)

    728x90

    안녕하세요

    로로봉입니다 : )

    오늘은 자바의 제어자 중 접근 지정자와 static에 대해 알아보겠습니다.

    접근 지정자는 클래스 자체나 클래스의 내부 구성 요소 앞에 위치해서 접근 범위를 지정하는 제어자 입니다.

    한편 static 제어자는 객체를 생성하지 않아도 클래스의 내부 구성 요소를 사용할 수 있도록 하는 제어자로 메모리에 어떻게 저장되는지 개념적으로 알아보도록 하겠습니다.


    1) 접근 지정자

    자바 제어자는 클래스, 필드, 메서드, 생성자 등에게 어떤 특징을 부여하는 문법 요소입니다.

    접근 지정자는 자바 제어자의 한 종류로 위에서 간단히 설명했듯이 클래스, 멤버, 생성자 앞에 위치하고 사용 범위를 정의하는 역할을 합니다.

    패키지를 학습할 때 다른 패키지에서 클래스를 사용할 수 있도록 public을 사용했었습니다.

    이렇게 public이라는 접근 지정자를 정의하여 클래스를 다른 패키지에서 사용할 수 있도록 정의했던 것입니다.

     

     

    ①  멤버 및 생성자의 접근 지정자

     멤버 및 생성자에는 public, protected, default(또는 package), private 라는 4가지 종류의 접근 지정자를 사용할 수 있습니다.

    4가지 접근 지정자 중에서 public의 사용 범위가 가장 넓으며, private가 가장 좁습니다.

    접근 범위는 public > protected > default > private 순입니다.

    접근 지정자 사용 가능 범위
    public 동일 패키지의 모든 클래스 + 다른 패키지의 모든 클래스에서 사용 가능
    protected 동일 패키지의 모든 클래스 + 다른 패키지의 자식 클래스에서 사용 가능
    default 동일 패키지의 모든 클래스에서 사용 가능
    private 동일 클래스에서 사용 가능

     

     

    ② 클래스의 접근 지정자

     클래스의 접근 지정자는 public, default 접근 지정자만 사용할 수 있습니다. class 키워드 앞에 public이 붙거나 붙어있지 않다면 default로 지정됩니다.

    앞에서와 마찬가지로 default 클래스는 같은 패키지 내에서만 사용할 수 있고, public 클래스는 다른 패키지에서도 사용할 수 있습니다.

    클래스를 default로 정의하면 다른 패키지에서 임포트 자체가 불가능합니다.

    다른 패키지에서 클래스를 사용하기 위해서는 해당 클래스를 임포트해야하므로 public으로 선언해야 합니다.

    ③ 클래스 접근 지정자와 생성자 접근 지정자의 연관성

     클래스 접근 지정자와 생성자 접근 지정자는 매우 밀접한 관련이 있습니다.

    클래스에서 생성자가 없을 때 컴파일러는 기본 생성자를 자동으로 추가한다고 학습했습니다.

    이때 자동으로 추가되는 생성자의 접근 지정자는 클래스의 접근 지정자에 따라 결정됩니다.

    직접 생성자를 정의할 때는 얼마든지 클래스와 생성자의 접근 지정자를 다르게 지정할 수 있습니다.

    하지만 둘중 하나라도 default 접근 지정자를 지정하면 다른 패키지에서는 불러와서 사용하기 어렵습니다.

    클래스가 default라면 임포트 자체가 불가능하고, 생성자가 default라면 객체를 생성할 수가 없어서 클래스를 사용할 수가 없게 됩니다.


    2) static 제어자

    static은 클래스의 멤버(필드, 메서드, 이너 클래스)에 사용하는 제어자입니다.

    지금까지 클래스의 멤버를 다른 클래스 내에서 사용하기 위해서는 가장 먼저 클래스의 객체를 생성해야 한다고 배웠습니다.

    이렇게 객체 안에 있을 때 사용할 수 있는 상태가 되는 멤버를 인스턴스 멤버라고 합니다.

    쉽게 말해서 인스턴스 멤버는 멤버 앞에 static이 붙어 있지 않은 것을 말합니다.

    반면 멤버 앞에 static이 붙으면 정적 멤버라고 합니다.

    정적 멤버는 객체의 생성 없이 '클래스명.멤버명'만으로 바로 사용할 수 있습니다.

    정작 멤버도 인스턴스 멤버처럼 객체를 먼저 생성한 후 '참조 변수명.멤버명'과 같이 사용할 수 있지만, 그렇게 사용할 것이라면 굳이 정적 멤버로 만들 필요가 없이 그냥 멤버로 정의해도 될 것입니다.

     

     

    ① 인스턴스 필드와 정적 필드

    인스턴스 필드와 정적 필드에 대해 보다 쉽게 이해해보겠습니다.

    class A {
       int m = 3;
       static int n = 5;
    }

    인스턴스 필드와 정적 필드는 사용하는 방법이 차이가 있습니다. 각 필드의 메모리의 저장 위치가 다른 것을 잘 알아두어야 합니다.

    메모리에서 인스턴스 필드와 정적 필드의 저장 공간 위치는 아래와 같습니다.

    [ 그림 1 : 인스턴스 필드와 정적 필드의 메모리 구조 ]

    인스턴스 필드를 사용하기 위해서는 먼저 객체를 생성한 후 '참조 변수명.필드명'과 같이 사용할 수 있습니다.

    인스턴스 필드인 m의 저장 공간은 객체 내부에 생성되므로 m을 사용하기 위해서는 반드시 객체를 먼저 생성해야 합니다.

    또한 저장 공간이 힙 메모리에 위치하므로 반드시 해당 저장 공간에 값을 읽거나 쓰기 위해서는 참조 변수명을 사용해야 합니다.

    반면 정적 필드는 '클래스명.필드명' 처럼 사용합니다. 참조 변수명이 아닌 클래스 자체의 명칭에 포인터지정자(.)를 사용하여 사용합니다.

    정적 필드인 n은 클래스 내부에 저장 공간을 지니고 있기 때문에 객체 생성 없이 바로 사용할 수 있습니다.

    A a = new A();
    System.out.print(a.m);	// 3
    
    System.out.print(A.n);	// 5

    메모리 구조에서 볼 수 있듯이 객체 내부에도 정적 필드인 n이 존재합니다. n의 실제 저장 공간은 정적 영역 내부에 있기 때문에 객체의 n은 실제 정적 필드의 주소 값을 가지고 있습니다.

    참조 변수명으로도 n을 a.n으로 사용할 수 있지만 정적 필드를 나타내기 위해서 가능하면 A.n으로 사용하는 것을 권장합니다.

    정적 필드의 가장 큰 특징은 객체 간에 공유 변수의 성질이 있다는 것입니다. 

    다음을 통해 정적 필드가 어떻게 공유되는 것인지 알아보겠습니다.

    A a1 = new A();
    A a2 = new A();
    
    a1.m = 5;
    a2.m = 6;
    System.out.println(a1.m);	// 5
    System.out.println(a2.m);	// 6
    
    a1.n = 7;
    a2.n = 8;
    System.out.println(a1.n);	// 8
    System.out.println(a2.n);	// 8
    
    A.n = 9;
    System.out.println(a1.n);	// 9
    System.out.println(a2.n);	// 9

    객체 a1과 a2를 생성하면 힙 메모리에 2개의 객체가 만들어 집니다. 이때 메모리 구조는 아래와 같습니다.

    [ 그림 2 : 2개의 객체가 1개의 정적 필드를 공유하는 메모리 구조 ]

    각각의 객체 안에는 멤버가 2개씩 있습니다. 인스턴스 필드 m은 객체 안에 실제 데이터 값을 저장합니다.

    정적 필드 n은 정적 영억의 A클래스 안에 실제 데이터 값이 있습니다. 각 객체에서는 n필드의 주소값을 저장하여 참조하게 됩니다. 그렇기 때문에 공유가 된다고 보면 됩니다.

    a1.n = 7을 이용해 정적 필드에 7을 대입하고 a2.n = 8으로 8을 대입한 경우 각 객체에서 n 필드의 같은 주소 값을 보기 때문에 a1.n와 a2.n은 마지막에 대입한 8의 값을 나타내게 됩니다.

    그 후에 A.n = 9 를 처리하게 되면 정적 필드 n에 9를 저장하는 것이므로 각 객체의 n 값을 읽으면 모두 9가 됩니다.

     

     

    ② 인스턴스 메서드와 정적 메서드

    이번에는 인스턴스 메서드와 정적 메서드를 비교해 보겠습니다.

    필드와 마찬가지로 인스턴스 메서드는 반드시 객체를 생성한 후에 사용할 수 있지만, 정적 메서드는 클래스명으로 바로 접근 할 수 있고, 인스턴스 메서드처럼 객체로도 호출 할 수 있습니다.

    class A {
       void abc() {
          System.out.println("instance 메서드");
       }
       static void bcd() {
          System.out.println("static 메서드");
       }
    }

    인스턴스 메서드인 void abc()는 객체를 꼭 생성해야 호출할 수 있지만 static으로 선언된 bcd() 메서드는 객체 생성 없이 호출 가능합니다.

    필드와 메서드의 차이는 모두 메모리의 첫 번째 영역에 위치한다는 것입니다.

    인스턴스 메서드는 인스턴스 메서드 영역, 정적 메서드는 클래스 내부에 존재하는 것만 차이가 있습니다.

    [ 그림 3 : 인스턴스 메서드와 정적 메서드의 메모리 구조 ]

     

     

    ③ 정적 메서드 안에서 사용할 수 있는 필드와 메서드

    정적 메서드 내에서는 정적 필드 또는 정적 메서드만 사용할 수 있습니다.

    인스턴스 필드나 인스턴스 메서드는 사용할수 없습니다.

    그 이유는 정적 멤버는 객체의 생성 없이 실행될 수 있습니다. 하지만 인스턴스 멤버는 반드시 객체를 생성한 후에 사용할 수 있습니다.

    만약 정적 메서드 내에서 인스턴스 멤버를 사용한다면 결국 정적 메서드도 객체를 생성한 후에 동작할 수 있을 것입니다.

    객체 생성 이전에 실행하려면 내부에는 객체 생성 이전에 사용할 수 있는 요소들로만 구성되어 있어야 합니다.

    그렇기 때문에 정적 메서드 내부에 정적 멤버만 올 수 있습니다.

    반면 인스턴스 메서드 내에서는 인스턴스 멤버와 정적 멤버 모두 사용할 수 있습니다.

    class A {
       int a;
       static int b;
       void abc() {
          // a, b, bcd(), cde() 사용 가능
       }
       static void bcd() {
          // b, cde() 사용 가능
       }
       static void cde() {
          // b, bcd() 사용 가능
       }
    }

    인스턴스 멤버 int a와 인스턴스 메서드 abc의 경우 객체를 반드시 생성하고 사용해야하고, static이 붙은 정적 멤버의 경우 객체 생성 전에도 사용할 수 있습니다.

    ④ 정적 초기화 블록

    일반적으로 인스턴스 필드의 초기화는 객체가 만들어지는 시점에 이뤄집니다.

    객체가 생성자에서 만들어지므로 생성자 내에 인스턴스 필드를 초기화하는 것이 일반적입니다.

    하지만 정적 필드는 객체의 생성 이전에도 사용할 수 있으므로 생성자가 호출되지 않은 상태에서 초기화가 필요합니다.

    생성자에서는 정적 필드를 초기화 할 수 없습니다. 그래서 정적 필드를 초기화하기 위한 문법이 있습니다.

    static {
       // 클래스가 메모리에 로딩될 때 실행되는 내용
    }

    정적 초기화 블록은 클래스가 메모리에 로딩될 때 가장 먼저 실행되므로 여기에 정적 필드의 초기화 코드를 넣어두면 클래스가 로딩되는 시점에 초기화 됩니다.

    ⑤ static main() 메서드

    예제를 작성하다보면 public static void main(String[] args) 메인 메서드를 많이 사용했을 것입니다.

    이 main() 메서드 또한 정적 메서드로 구성된 것입니다.

    main() 메서드가 정적 메서드이므로 자바 가상 머신은' 실행 클래스명.main()'을 호출하여 main() 메서드가 실행되게 됩니다.

    자바 가상 머신이 프로그램을 실행할 때 가장 먼저 '클래스명.main()'으로 정적 메서드 호출 방식으로 호출하기 때문에 main 메서드를 static으로 정적 메서드로 구성해야만 하는 것입니다.

     

    공감 ♥ + 구독부탁드립니다 : )

    728x90
    반응형

    댓글