개발 공부/Java & Spring

5. 제어자(modifier)

빵다희 2022. 10. 24. 17:13

제어자란?

  • 제어자는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여하는 것이다.
  • 제어자는 클래스나 멤버변수와 메서드에 주로 사용되며, 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능 하다.
  • 제어자의 종류는 크게 접근 제어자와 그 외의 제어자로 나눌 수 있다.
접근 제어자
public, protected, default, private
그 외
static, final, abstract, native, transient, synchronized, volatile, strictfp
  • 접근 제어자는 한번 사용할 때 네 가지 중 하나만 선택해서 사용할 수 있다.
  • 제어자들 간의 순서는 관계없지만 주로 접근 제어자를 제일 왼쪽에 놓는다.

 

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

  • static은 '클래스의' 또는 '공통적인'인 의미를 가지고 있다.
  • 클래스변수(static멤버변수)는 인스턴스에 관계없이 같은 값을 갖는다. 그 이유는 하나의 변수를 모든 인스턴스가 공유하기 때문이다.
  • static이 사용될 수 있는 곳은 멤버변수, 메서드, 초기화 블럭이다.
  • static이 붙은 멤버변수와 메서드, 그리고 초기화 블럭은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있다.
  • 인스턴스메서드와 static메서드의 근본적인 차이는 메서드 내에서 인스턴스 멤버를 사용하는가의 여부에 있다.
  • 인스턴스 멤버를 사용하지 않는 메서드는 static 제어자를 붙여서 static메서드로 선언하는 것을 고려해볼 필요가 있다.
  • 왜냐하면 static 메서드로 하는 것이 인스턴스를 생성하지 않고도 호출이 가능해서 더 편리하고 속도도 더 빠르기 때문이다.
  • static초기화 블럭은 클래스가 메모리에 로드 될 때 단 한번만 수행되며, 주로 클래스변수를 초기화하는데 주로 사용된다.

 

* static 지시어 사용예시

class StaticTest {
  static int width = 200;           // 클래스 변수(static 변수)
  static {                          // 클래스 초기화 블럭
    // static변수의 초기화 코드 작성 
  }
  static int max(int a, int b) {    // 클래스 메서드(static 메서드 )
    return a > b ? a : b;
  }
}
 

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

  • final은 '마지막의' 또는 '변경될 수 없는' 의미를 가지고 있다.
  • final이 사용될 수 있는 곳 : 변수(멤버변수, 지역변수), 메서드, 클래스
  • 변수에 사용되면 값을 변경할 수 없는 상수가 된다,
  • 메서드에 사용하면 오버라이딩을 통해 재정의 될 수 없게 된다.
  • 클래스에 사용하면 다른 클래스의 조상이 될 수 없다.
  • 대표적인 final클래스로는 String과 Math가 있다.

 

생성자를 이용한 final멤버 변수의 초기화

final이 붙은 변수를 초기화하는 방법은 클래스 내에 매개변수를 갖는 생성자를 선언하여,

인스턴스를 생성할 때 final이 붙은 멤버변수를 초기화하는데 필요한 값을 생성자의 매개변수로 제공받는것이다.

이 기능을 활용하면 각 인스턴스마다 final 멤버변수가 다른 값을 갖도록 하는 것이 가능하다.

class Card {
  final int NUMBER;              // 선언과 동시에 초기화하지 않고 생성자에서 초기화
  final String KIND;

  Card (String kind, int num){   // 매개변수로 넘겨받은 값으로 상수를 초기화한다. 
      KIND = kind;
      NUMBER = num;  
  }
}

class FinalCardTest {
  public static void main (String args[]){
  Card c = new Card("HEART", 10);
  // c.NUMBER = 5;               // 상수에 값을 할당할 수 없어서 에러 발생.
  System.out.println(c.NUMBER);  // 값이 변경되지 않고, 출력결과는 10
  }     
}
 

abstract [추상의, 미완성의]

  • abstract는 '미완성'의 의미를 가지고 있다.
  • abstract가 사용될 수 있는 곳 : 클래스, 메서드
  • 클래스에 사용되면 클래스 내에 추상 메서드가 선언되어 있음을 의미할 수 있다.
  • 메서드에 사용되면 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.
  • 추상 클래스는 미완성의 메소드를 포함하고 있으므로 인스턴스를 생성할 수 없다.
  • 드물게 추상메소드가 없는 추상클래스가 있을 수 있다. java.awt.event.WindowAdapter가 그 예시인데, 이 클래스는 아무런 내용이 없는 메소드들만 정의되어있다. 이런 클래스는 인스턴스를 생성해봤자 할 수 있는게 아무것도 없기 때문에 'abstract' 제어자를 붙여 인스턴스를 생성하지 못하게 하였다. 이 클래스 자체로는 쓸모가 없지만 이 클래스를 상속받아 일부의 원하는 메서드만 오버라이딩해도 된다는 장점이 있다.

 

접근 제어자(access modifier)

  • 접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다.
  • 접근 제어자가 사용될 수 있는 곳 : 클래스, 멤버변수, 메서드, 생성자
  • 클래스나 멤버변수, 메서드, 생성자에 접근 제어자를 지정해주지 않는다면 기본적으로 'default'가 지정된다.
  • private : 같은 클래스 내에서만 접근이 가능.
  • default : 같은 패키지 내에서만 접근 가능.
  • protected : 같은 패키지 내에서 또는, 패키지에 상관없이 상속관계에 있는 자손클래스에서 접근 가능
  • public : 접근 제한이 없다.
public > protected > (default) > private 순으로 접근범위가 좁아진다.

 

접근 제어자를 이용한 캡슐화

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

1. 외부로부터 데이터를 보호하기 위해서

2. 외부에는 불필요하지만 내부적으로만 사용되는 부분을 감추기 위해서 이다.

이것을 데이터 감추기(data hiding)라고 하며, 객체지향개념의 캡슐화에 해당하는 내용이다.

접근범위가 넓을수록 메소드나 변수의 변경이 있을 때, 테스트해야하는 범위가 넓다. 접근제어자를 적절히 선택하여 접근 범위를 최소화하도록 해야한다.

멤버변수의 값에 변동이 있을 때마다 유효성검사를 해야한다면 멤버변수를 private나 protected로 제한하고 멤버변수의 값을 읽고 변경할 수 있는 public메서드를 제공함으로써 간접적으로 멤버변수의 값을 다룰 수 있도록 하는것이 바람직하다.

접근제어자를 private나 protected로 지정한다면 a = b; 같이 멤버변수로의 직접적인 접근은 허가되지 않는다.

메소드를 통한 접근만이 허용된다.

public class Time {
  private int hour;     // 접근 제어자를 private으로 하여 외부로부터의 직접적인 접근을 막음.
  protected int minute; // 상속을 통해 확장이 예상되는 클래스라면 변수에 protected를 사용.

  public int getHour(){return hour;}   // 보통 멤버변수의 값을 읽는 메서드의 이름을 
  public void setHour(int hour){       // 'get멤버변수이름'으로 한다. 이를 겟터(getter)라고한다.
    if (hour < 0 || hour > 23) return;
    this.hour = hour; 
  }
  public int getMinute(){return minute;} 
  public void setMinute(int minute){         // 멤버변수의 값을 변경하는 메서드의
    if (minute < 0 || minute > 59) return;   // 이름을 'set멤버변수이름'으로 한다.
    this.minute = minute;                    // 이를 셋터(setter)라고 한다. 
  }
}
 

생성자의 접근 제어자

생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다.

생성자의 접근 제어자를 private으로 지정하면, 인스턴스는 생성 할 수 없지만 클래스 내부에서는 인스턴스를 생성할 수 있다.

대신 외부에는 인스턴스를 생성해서 반환해주는 public 메서드를 제공함으로써 외부에서 인스턴스를 사용하도록 할 수 있다.

class Singleton {
     ...
  // getInstance()에서 사용될 수 있도록 인스턴스가 미리 생성되어야 하므로 static이어야 한다.
  private static Singleton s = new Singleton();
  private Singleton(){
     ... 
  }
  // 인스턴스가 생성되지 않아도 호출할 수 있어야 하므로 static이어야 한다.
  public static Singleton getInstance(){
    return s;
  }
}
 

위 예시처럼 생성자를 통해 인스턴스 생성을 하지 못하게 하고, public메서드를 통해 인스턴스에 접근하게 함으로써 사용할 수 있는 개수를 제한할 수 있다.

주의할 점은 생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없다. 이유는

자손클래스의 인스턴스가 생성될 때 조상클래스의 생성자를 호출해야하는데 제어자가 private가 이므로 자손클래스에서 접근을 못하기 때문이다.

그래서 클래스 앞에 final을 더 추가하여 상속할 수 없는 클래스라는 것을 알리는 것이 좋다.

 

대상에 따라 사용할 수 있는 제어자

대상
사용가능한 제어자
클래스
public, (default), final, abstract
메서드
모든 접근 제어자, final, abstract, static
멤버변수
모든 접근 제어자, final, static
지역변수
final

 

제어자 조합시, 주의사항

1. 메서드에 static과 abstract를 함께 사용할 수 없다

: static은 몸통이 있는 메서드에만 사용할 수 있음

2. 클래스에 abstract와 final을 동시에 사용할 수 없다.

: final은 내용이 고정되어 확장할 수 없다는 의미이고 abstract는 상속을 통해서 완성이 되어야한다는 의미이기때문에 서로 모순된다.

3. abstract메서드의 접근 제어자가 private일 수 없다.

: abstract메서드는 자손클래스에서 구현해야하는데 접근제어자가 private이면 자손클래스에서 접근 할 수 없다.

4. 메서드에 private과 final을 같이 사용할 필요는 없다.

: 접근제어자 private와 final인 메서드는 오버라이드 될 수 없는데 이 둘 중 하나만 사용해도 의미가 충분하다.

 

728x90
반응형