본문 바로가기
개념서/Java

[Java] 제어자

by 사서T 2022. 6. 13.

제어자란?

   제어자(modifier)는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다. 제어자는 크게 접근 제어자와 그 외의 제어자로 나뉘며, 하나의 대상에 여러 제어자를 조합하여 사용하는 것이 가능하다. 하지만 접근 제어자는 한 대상에 하나만 사용 가능하다. 제어자는 다음과 같다.

 

  • 접근 제어자 : public, protected, default, private
  • 그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

 

※ 제어자들 간의 순서는 관계없지만 주로 접근 제어자를 제일 왼쪽에 위치 시킨다.


static

   자바에서 static은 '클래스의' 또는 '공통적인'의 의미를 지닌다. 즉, static 제어자가 붙은 멤버는 인스턴스에 상관없이 공통적으로 사용되는 멤버라는 의미로 클래스에 종속되어 있음을 의미한다. 따라서 static이 붙은 멤버 변수와 메서드는 인스턴스 생성없이 접근이 가능하다. 이 말에 내포된 또 다른 의미는 'static 메서드 내에선 아직 인스턴스가 생성되지 않은 상태일 수 있기 때문에 인스턴스 멤버의 사용이 불가능하다'는 것이다. 추가적으로 인스턴스 멤버를 사용하지 않은 메서드는 static을 붙이는 것을 고려해보자.

   static 예제와 내용 정리는 다음과 같다.

 

class StaticTmp {
    static int x = 10;
    int y = 5;
    
    static {
        //클래스 초기화 블럭으로 클래스 로드시 수행
    }
    
    static int method(int num) {
        //y += num; static 메서드 내에서 인스턴스 변수 호출 시 에러
        return num + 10;
    }
}

//인스턴스 생성없이 접근 가능
StaticTmp.x; // 10
StaticTmp.method(10); // 20

 

※ static은 멤버 변수, 메서드, 초기화 블럭에 사용 가능하다.


final

   자바에서 final은 '마지막의' 또는 '변경될 수 없는'의 의미를 지닌다. 간단한 예제와 표를 통해 특징을 알아보자.

 

//상속(확장) 불가능한 클래스
final class FinalTmp {
    //상수
    final int MAX_NUM = 100;
    
    //오버라이딩 불가능한 메서드
    final void getMaxNum() {
        final int TMP = MAX_NUM;
        return MAX_NUM;
    }
}

 

※ final은 멤버 변수, 지역 변수, 메서드, 클래스에 사용 가능하다.

 

   상수는 일반적으로 선언과 동시에 초기화하지만 생성자를 이용해 초기화 가능하다. 이를 이용해 인스턴스마다 상수가 다른 값을 갖도록 할 수 있다.

 

class FinalTmp {
    static final int AVG = 5; //클래스 상수
    final int MAX = 10; //인스턴스 상수
    final int MIN;
    
    //생성자를 이용한 상수 초기화
    FinalTmp(int Num) {
        MIN = Num; //OK
        this.MIN = Num; //OK
        MAX = Num; //에러 : 상수가 초기화돼있는 경우
    }
}

abstract

   자바에서 abstract는 '미완성'이라는 의미를 지닌다. 클래스에 사용되면 추상 클래스, 메서드에 사용되면 추상 메서드가 된다. 추상 클래스는 미완성이기에 인스턴스 생성이 불가능한 클래스임을 의미하고, 추상 메서드는 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 것이다. 만약 클래스에 추상 메서드가 존재하는 경우 클래스는 반드시 추상 클래스임을 명시해야 한다.

   추상 클래스를 상속받은 클래스는 추상 클래스의 추상 메서드를 반드시 오버라이딩해야 한다. 즉, 추상 클래스는 상속받는 클래스들에게 구현해야 하는 목록을 제공하는 역할도 수행하고, 메서드의 구현을 강제함으로써 개발자가 이를 인식할 수 있게 보조한다.

   간단한 예제와 표를 통해 특징을 알아보자.

 

//추상 메서드가 존재함을 명시하기위해 class 앞에 abstract를 붙임
abstract class 클래스이름 {
    //추상 메서드(선언부만 존재)
    abstract 리턴타입 메서드이름(); //구현부인 블록{}이 없음
}

 

 

※ abstract는 메서드, 클래스에 사용 가능하다.

 

   추상 메서드를 사용하지 않은 경우에도 클래스에 abstract를 붙이는 경우가 존재한다. 클래스에 존재하는 메서드들이 형태는 있지만 실제 내용은 없는 빈 블록인 경우이다. 이 경우 인스턴스를 생성해도 할 수 있는 것이 없기 때문에 일부러 abstract를 붙여 인스턴스 생성을 제한한 것이다. 이 경우 상속받는 클래스에게 모든 추상 메서드의 오버라이딩을 강제하지 않고 필요한 메서드만 오버라이딩하여 사용할 수 있다는 장점이 있다.

 

//추상 메서드가 없는 추상 클래스 예시
public abstract class WindowAdapter
	implements WindowListener, WindowStateListener, WindowFocusListener {
		
      public void windowOpened(WindowEvent e) {}
      public void windowClosed(WindowEvent e) {}
      ...
}

 

<추상화 정리>

  • 추상 클래스는 인스턴스의 생성이 제한된다.
  • 추상 메서드는 추상 클래스를 상속받은 클래스에서 반드시 오버라이딩해야 한다.
  • 추상화란 클래스간의 공통점을 찾아내서 공통의 상위 클래스를 만드는 작업이다.

접근 제어자(access modifier)

   멤버 또는 클래스에 대한 외부 접근을 제한할 때 사용한다. 외부로부터의 접근을 제한함으로써 클래스 내부의 데이터를 보호할 수 있고, 클래스 내에서만 사용되는 멤버들을 감춰 복잡성을 줄일 수 있다. 이 것을 객체지향개념의 캡슐화라고 한다. 간단하게 캡슐화란 클래스에 접근하는 대상에게 필요한 정보만을 제공하는 것이다.

 

   접근 제어자의 종류는 4가지로 다음과 같다.

 

※ 접근 제어자 범위 : public > protected > (default) > private

 

   접근 제어자를 사용할 때 '어떤 접근 제어자를 사용할 것인가'를 선택하는 것은 매우 중요하다. 만약 메서드가 변경되었다면, 메서드에 붙은 접근 제어자에 따라 테스트할 범위가 달라지기 때문이다. public이 사용되었다면 해당 메서드를 사용한 모든 부분에 대해 테스트를 해야 하고, dafulat면 같은 패키지 내에서 사용한 부분에 대해, private면 클래스 내에서만 확인하면 된다. 이처럼 접근 제어자에 따라 테스트 범위가 매우 달라지며 접근 제어자를 통해 어느 범위까지 사용이 가능한 지 명시적으로 확인할 수도 있다.

 

   캡슐화의 특징 중 데이터를 보호한다는 개념이 있었다. 간단한 예시를 통해 이해를 심화해보자.

 

class Time {
    public int hour = 10;
}

Time time = new Time();
time.age = 25; //데이터에 대한 무분별한 접근

 

   위의 예제는 Time 클래스의 멤버 변수인 hour에 대해 직접 접근하여 데이터를 수정하는 경우이다. hour은 0이상 24 미만의 범위를 가져야 하지만 직접 접근하여 범위를 초과하는 값을 넣고 있고 이것을 막을 방법이 없다. 이처럼 데이터에 대한 무분별한 접근은 예기치 못한 상황으로 이어질 수 있다. 위 코드를 접근을 제한한 코드로 수정해보자.

 

class Time {
    private int hour = 10;
    
    public int getHour() {
        return hour;
    }
    
    public void setHour(int hour) {
        if (0 <= hour && hour < 24)
            this.hour = hour;
    }
}

Time time = new Time();
System.out.println(time.getHour()); //10
time.setHour(25); //범위를 벗어나기 때문에 hour가 변경되지 않음
System.out.println(time.getHour()); //10

 

   인스턴스 변수의 접근 제어자를 private로 변경하고 이에 대한 접근으로 public 메서드인 getHour()와 setHour()를 추가하였다. getHour()는 인스턴스 변수의 정보를 가져오고, setHour()는 인스턴스 변수의 정보를 수정하는 역할을 한다. 이로써 데이터에 대한 무분별한 접근과 옳바른 수정이 가능해졌다.

 

   생성자에 접근 제어자를 붙이면 어떻게 될까? 결과는 인스턴스의 생성을 제한할 수 있다. 만약 생성자에 private를 붙이면 외부에서 생성자에 접근할 수 없기 때문에 인스턴스 생성이 불가능해진다. 내부에서는 인스턴스 생성이 가능하기에 클래스 내부에서 인스턴스를 생성하고 필요에 따라 이를 반환하는 형식으로 작성이 가능하다. 이 경우 다음과 같은 특징이 있다.

 

  • 인스턴스의 개수를 제한할 수 있다.
  • 생성자가 private인 경우 다른 클래스가 해당 클래스를 상속할 수 없다.

   하위 클래스의 인스턴스 생성 시 상위 클래스의 생성자를 호출해야 하지만 이 경우 상위 클래스의 생성자가 private가 붙어 접근이 제한된다. 따라서 상속이 불가능해지고 이를 명시하기 위해 상위 클래스에 final을 붙여주어야 한다.

 

   제어자들의 조합에 대한 몇 가지 주의사항을 살펴보자. 위의 내용을 충분히 이해했다고 가정하고 이유는 생략했다.

 

  • 메서드에 static과 abstract를 함께 사용할 수 없다.
  • 클래스에 abstract와 final을 동시에 사용할 수 없다.
  • abstract 메서드의 접근 제어자가 private일 수 없다.
  • 메서드에 private과 final을 같이 사용할 필요는 없다.

접근 제어자 완벽 이해하기

   접근 제어자에 대해 완벽하게 이해했는 지 테스트해보기 위해 에서 나온 예제를 풀어보자. 해설은 여기서 확인 가능하며 더 자세한 내용을 책을 직접 읽어보는 것을 추천한다. 빈 칸에는 호출이 가능한 지 여부를 O,X로 표시하면 된다. 첫번째 칸은 "PackageOne에 존재하는 ClassA의 method() 내에서 ClassA의 접근 제어자가 pri(private)인 인스턴스 멤버의 호출이 가능한가?"를 물어보는 것이다. 상속의 개념 또한 정확히 이해하고 풀어보길 권장한다.

 

ClassAA : ClassA를 상속받음, ClassAB : ClassA를 상속받고 다른 패키지에 존재

method() : 인스턴스 메서드, staticMethod() : 클래스 메서드

※ pri = private, def = default, pro = protected, pub = public

 

   답은 아래에서 확인하자.


Q&A

Q. 제어자란?

A. 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여하는 것이다.

 

Q. static이란?

A. '클래스'의 의미를 포함하는 제어자로 해당 제어자가 붙은 멤버는 클래스에 종속되어 인스턴스의 생성에 상관없이 사용이 가능하다. 이 말은 'static 메서드에선 인스턴스의 생성 여부를 판단할 수 없기에 인스턴스 멤버의 사용이 불가능하다'라는 의미를 내포한다.

 

Q. final이란?

A. '변경될 수 없는'의 의미를 포함하는 제어자로 변수에 붙는 경우 상수, 메서드에 붙는 경우 오버라이딩 불가능, 클래스에 붙는 경우 상속이 불가능함을 의미한다.

 

Q. abstract란?

A. '미완성'의 의미를 포함하는 제어자로 메서드에 붙는 경우 하위 클래스에게 해당 메서드의 오버라이딩을 강제하고, 클래스에 붙는 경우 해당 클래스의 인스턴스 생성을 제한한다.

 

Q. 추상화란?

A. 클래스간의 공통점을 찾아내서 공통의 상위 클래스를 만드는 작업이다.

 

Q. 접근 제어자란?

A. 멤버 또는 클래스에 대한 외부 접근을 제한할 때 사용하며 private, default, protected, public의 순서로 제한 범위가 넓어진다. 범위는 private는 클래스 내에서, default는 같은 패키지 내, protected는 같은 패키지 내와 다른 패키지의 하위 클래스 내, public은 제한이 없다.

 

Q. 캡슐화란?

A. 외부에서 클래스에 접근할 때 필요한 기능만을 제공하여, 데이터에 대한 무분별한 접근과 클래스 내부에서만 사용하는 불필요한 멤버에 대한 인지를 막는 것이다. 따라서 예기치 못한 데이터 수정과 클래스에 대한 복잡성을 줄일 수 있다.


참고자료

  • Java의 정석
  • 스프링 입문을 위한 자바 객체지향의 원리와 이해

'개념서 > Java' 카테고리의 다른 글

[Java] 인터페이스  (0) 2022.06.23
[Java] 다형성  (0) 2022.06.17
[Java] package와 import  (0) 2022.06.10
[Java] 상속  (0) 2022.06.10
[Java] 객체지향 프로그래밍 I  (0) 2022.06.06

댓글