4. 제어자(modifier)
4.1 제어자란?
제어자(modifier)는 클래스, 변수 또는 메서드의 선언부에 함께 사용되어 부가적인 의미를 부여한다. 제어자의 종류는 크게 접근 제어자와 그 외 제어자로 나눌 수 있다.
접근 제어자 - public, protected, default, private
그 외 - static, final, abstract, native, transient, synchronized, volatile, stricftfp
제어자는 클래스나 멤버변수와 메서드에 주로 사용되며, 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능하다. 단, 접근 제어자는 한번에 네 가지 중 하나만 선택해서 사용할 수 있다.
※ 제어자들 간의 순서는 관계 없지만 주로 접근 제어자를 제일 왼쪽에 놓는 경향이 있다.
4.2 static - 클래스의, 공통적인
static은 '클래스의' 또는 '공통적인'의 의미를 가지고 있다. 인스턴스 변수는 하나의 클래스로부터 생성되었더라도 각기 다른 값을 유지하지만, 클래스변수(static 멤버변수)는 인스턴스에 관계없이 같은 값을 갖는다. 그 이유는 하나의 변수를 모든 인스턴스가 공유하기 때문이다.
static이 붙은 멤버변수와 메서드, 그리고 초기화 블럭은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있다.
| 제어자 | 대상 | 의미 |
| static | 멤버변수 | -모든 인스턴스에 공통적으로 사용되는 클래스변수가 된다. -클래스변수는 인스턴스를 생성하지 않고도 사용 가능하다. -클래스가 메모리에 로드될 때 생성된다. |
| 메서드 | -인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다. -static메서드 내에서는 인스턴스멤버들을 직접 사용할 수 없다. |
4.3 final - 마지막의, 변경될 수 없는
final은 '마지막의' 또는 '변경될 수 없는'의 의미를 가지고 있으며 거의 모든 대상에 사용될 수 있다. 변수에 사용되면 값을 변경할 수 없는 상수가 되며, 메서드에 사용되면 오버라이딩을 할 수 없게 되고 클래스에 사용되면 자신을 확장하는 자손클래스를 정의하지 못하게 된다.
| 제어자 | 대상 | 의미 |
| final | 클래스 | 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다. 그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다. |
| 메서드 | 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다. | |
| 멤버변수 | 변수 앞에 final이 붙으면 값을 변경할 수 없는 상수가 된다. | |
| 지역변수 |
생성자를 이용한 final멤버 변수의 초기화
final이 붙은 변수는 일반적으로 선언과 초기화를 동시에 하지만, 인스턴스변수의 경우 생성자에서 초기화될 수 있도록 할 수 있다. 이 기능을 활용하면 각 인스턴스마다 final이 붙은 멤버변수가 다른 값을 갖도록 하는 것이 간으하다. 만일 이것이 불가능하다면 클래스에 선언된 final이 붙은 인스턴스변수는 모든 인스턴스에서 같은 값을 가져야만 할 것이다.
4.4 abstract - 추상의, 미완성의
abstract는 '미완성'의 의미를 가지고 있다. 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상메서드를 선언하는데 사용된다. 그리고 클래스에 사용되어 클래스 내에 추상메서드가 존재한다는 것을 쉽게 알 수 있게 한다.
| 제어자 | 대상 | 의미 |
| abstract | 클래스 | 클래스 내에 추상 메서드가 선언되어 있음을 의미 |
| 메서드 | 선언부만 작성하고 구현부는 작성하지 않는 추상 메서드임을 의미 |
4.5 접근제어자(access modifier)
접근제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다. 접근제어자가 default임을 알리기 위해 실제로 default를 붙이지는 않는다. 클래스나 멤버변수, 메서드, 생ㅅ어자에 접근 제어자가 지정되어 있지 않다면, 접근제어자가 default임을 뜻한다
접근 범위가 넓은쪽에서 좁은 쭉의 순으로 나열하면 public > protected > (default) > private이다.

| 제어자 | 같은 클래스 | 같은 패키지 | 자손클래스 | 전 체 |
| public | O | O | O | O |
| protected | O | O | O | |
| (default) | O | O | ||
| private | O |
※ 대상에 따라 사용할 수 있는 접근 제어자
| 대 상 | 사용가능한 접근 제어자 |
| 클래스 | public, (default) |
| 메서드 | public, protected, (default), private |
| 멤버변수 | |
| 지역변수 | 없 음 |
접근 제어자를 이용한 캡슐화
클래스나 멤버, 주로 멤버에 접근 제어자를 사용하는 이유는 클래스 내부에 선언된 데이터를 보호하기 위해서이다. 데이터가 유효한 값을 유지하도록, 또는 비밀번호와 같은 데이터를 외부에서 함부로변경하지 못하도록 하기 위해서는 외부로부터의 접근을 제한하는 것이 필요하다. 이것을 데이터감추기(data hiding)라고 하며, 객체지향개념의 캡슐화(encapsulation)에 해당하는 내용이다.
또 다른 이유는 클래스 내에서만 사용되는, 내부 작업을 위해 임시로 사용되는 멤버변수나 부분작업을 처리하기 위한 메서드 등의 멤버들을 클래스 내부에 감추기 위해서이다. 외부에서 접근할 필요가 없는 멤버들을 private로 지정하여 외부에 노출시키지 않음으로써 복잡성을 줄일 수 있다.
접근 제어자를 사용하는 이유
-외부로부터 데이터를 보호하기 위해서
-외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해서
생성자의 접근 제어자
생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다. 보통 생성자의 접근 제어자는 클래스의 접근 제어자와 같지만, 다르게 지정할 수도 있다. 생성자의 접근제어자를 private으로 지정하면, 외부에서 생성자에 접근할 수 없으므로 인스턴스를 생성할 수 없게 된다. 그래도 클래스 내부에서는 인스턴스를 생성할 수 있다. 대신 인스턴스를 생성해서 반환해주는 public메서드를 제공함으로써 외부에서 이 클래스의 인스턴스를 사용하도록 할 수 있다. 아래 메서드는 public인ㄷ 동시에 static이어야 한다.

이처럼 생성자를 통해 직접 인스턴스를 생성하지 못하게 하고 public메서드를 ㅌ오해 인스턴스에 접근하게 함으로써 사용할 수 있는 인스턴스의 개수를 제한할 수 있다. 또한 생성자가 private인 클래스는 다른 클래스의 조상이 될 수 없다. 생성자의 접근 제어자가 private이므로 자손클래스에서 호출하는 것이 불가능하기 때문이다. 그래서 클래스 앞에 final을 더 추가하여 상속할 수 없는 클래스라는 것을 알리는 것이 좋다.
4.6 제어자(modifier)의 조합
※ 대상에 따라 사용할 수 있는 제어자
| 대상 | 사용가능한 제어자 |
| 클래스 | 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인 메서드는 오버라이딩 될 수 없기 때문. 둘 중 하나만 사용해도 의미가 충분하다
5. 다형성(polymorphism)
5.1 다형성이란?
객체지향개념에서 다형성이란 '여러가지 형태를 가질 수 있는 능력'을 의미하며, 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 함으로써 다형성을 프로그램적으로 구현하였다. 이를 좀더 구체적으로 말하면, 조상클래스 타입의 참조변수로 인스턴스를 참조할 수 있도록 하였다는 것이다.

Tv클래스와 CaptionTv클래스가 이와 같이 정의되어 있을 때, 두 클래스간의 관계를 그림으로 나타내면 아래와 같다.

지금까지 Tv인스턴스를 다루기 위해서는 Tv타입의 참조변수를 사용하고, CaptionTv인스턴스를 다루기 위해서는 CaptionTv타입의 참조변수를 사용했다. 이 것이 보통이지만, Tv와 CaptionTv클래스가 서로 상속관계에 있을 경우, 다음과 같이 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조하도록 하는 것도 가능하다.
CaptionTV c = new CaptionTv();
Tv t = new CaptionTv(); //조상 타입의 참조변수로 자손 인스턴스를 참조
위 경우 실제 인스턴스가 Caption타입이라 할지라도, 참조변수 t로는 CaptionTv인스턴스의 모든 멤버를 사용할 수 없다. Tv타입의 참조변수로는 CaptionTv인스턴스 중에서 Tv클래스의 멤버들(상속받은 멤버 포함)만 사용할 수 있다. 따라서 생성된 CaptionTv인스턴스의 멤버 중에서 Tv클래스에 정의되지 않은 멤버, text, caption()은 참조변수 t로 사용이 불가능하다. (t.text t.caption()사용불가) 둘 다 같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.

CaptionTv c =new Tv();
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 불가능하다. 코드를 컴파일하면 에러가 발생한다. 그 이유는 실제 인스턴스인 Tv의 멤버 개수보다 참조변수 c가 사용할 수 있는 멤버 개수가 더 많기 때문이다. 그래서 이를 허용하지 않는다. c가 참조하고 있는 인스턴스는 Tv타입이고, Tv타입의 인스턴스에는 text와 caption()이 존재하지 않기 때문에 이들을 사용하려면 문제가 발생한다.
그래서 자손 타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 존재하지 않는 멤버를 사용하고자 할 가능성이 있으므로 허용하지 않는 것이다. 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있다.
반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.
5.2 참조변수의 형변환
기본형 변수와 같이 참조변수도 형변환이 가능하다. 단, 서로 상속관계에 있는 클래스사이에서만 가능하기 때문에 자손타입의 참조변수를 조상타입의 참조변수로, 조상타입의 참조변수를 자손타입의 참조변수로의 형변환만 가능하다.
※ 바로 윗 조상이나 자손이 아닌, 조상의 조상으로도 형변환이 가능하다. 따라서 모든 참조변수는 모든 클래스의 조상인 ObJECT클래스 타입으로 형변환이 가능하다.
자손타입 → 조상타입 (Up - casting) : 형변환 생략 가능
자손타입 ← 조상타입 (Down - casting) : 형변환 생략 불가
조상타입의 참조변수를 자손타입의 참조변수로 변환하는 것을 다운캐스팅(down - casting)이라고 하며, 자손타입의 참조변수를 조상타입의 참조변수로 변환하는 것을 업 캐스팅(up-casting)이라고 한다. 참조변수간의 형변환 역시 캐스트 연산자를 사용하며, 괄호()안에 변환하고자 하는 타입의 이름(클래스명)을 적어주면 된다.


위 코드의 Car, FireEngine, Ambulance가 정의되어 있을 때, 세 클래스간의 관계를 그림으로 표현하면 아래와 같다.
Car클래스는 FireEngine클래스와 Ambulance클래스의 조상이다. 그렇다고해서 FireEngine과 Ambulance가 형제관계는 아니다. 자바에서는 조상과 자식관계만 존재하기 때문에 서로 아무런 관계가 없다. 두 타입의 참조변수 간에는 형변환이 불가능하다.
형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 대문에 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않는다. 단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 개수를 조절하는 것뿐이다.\
서로 상속관계에 있는 타입간의 형변환은 양방향으로 자유롭게 수행될 수 있으나 참조변수가 가리키는 인스턴스의 자손타입으로 형변환은 허용되지 않는다. 그래서 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.
5.3 instanceof연산자
참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof연산자를 사용한다. 주로 조건문에 사용되며, instanceof의 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치한다. 그리고 연산의 결과로 boolean값인 true와 false중의 하나를 반환한다. instanceof를 이요한 연산결과로 true를 얻었다는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 것을 의미한다.

위 코드는 Car타입의 참조변수 c를 매개변수로 하는 메서드이다. 이 메서드가 호출될 때 매개변수로 Car클래스 또는 그 자손 클래스의 인스턴스를 넘겨받겠지만 메서드 내에서는 정확히 어떤 인스턴스인지 알 길이 없다. 그래서 instanceof연산자를 이용해서 참조변수 c가 가리키고 있는 인스턴스의 타입을 체크하고, 적절히 형변환다음에 작업을 해야 한다.
조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있기 때문에, 참조변수의 타입과 인스턴스의 타입이 항상 일치하지는 않는다는 것을 배웠다. 조상타입의 참조변수로는 실제 인스턴스의 멤버들을 모두 사용할 수 없기 때문에, 실제 인스턴스와 같은 타입의 참조변수로 형변환을 해야만 인스턴스의 모든 멤버들을 사용할 수 있다.

생성된 인스턴스는 FireEngine타입인데도, Object타입과 Car타입의 instanceof연산에서도 true로 결과를 얻었다. 그 이유는 FireEngine클래스는 Object클래스와 Car클래스의 자손클래스이므로 조상의 멤버들을 상속받았기 때문에, FireEngine인스턴스는 Object인스턴스와 Car인스턴스를 포함하고 있는 셈이기 때문이다.
실제 인스턴스와 같은 타입의 instanceof연산 이외에 조상타입의 instanceof연산에도 true를 결과로 얻으며, instanceof연산의 결과가 true라는 것은 검사한 타입으로의 형변환을 해도 아무런 문제가 없다는 뜻이다.
어떤 타입에 대한 instanceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.
5.4 참조변수와 인스턴스의 연결
조상 타입의 참조변수와 자손 타입의 참조변수의 차이점이 사용할 수 있는 멤버의 개수에 있다고 배웠다. 조상클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자손 클래스에 중복으로 정의했을 때, 조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조변수로 자손 인스턴스를 참조하는 경우 서로 다른 결과를 얻는다.
메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.
※ static메서드는 static변수처럼 참조변수의 타입에 영향을 받는다. 참조변수의 타입에 영향을 받지 않는 것은 인스턴스메서드 뿐이다. 그래서 static메서드는 반드시 참조변수가 아닌 '클래스이름.메서드()'로 호출해야 한다.
멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손클래스에 선언된 멤버변수가 사용된다. 하지만 중복 정의되지 않은 경우, 조상타입의 참조변수를 사용했을 때와 자손타입의 참조변수를 사용했을 때의 차이는 없다.

자손클래스 Child에 선언된 인스턴스변수 x와 조상 클래스 Parent로 부터 상속받은 인스턴스변수x를 구분하는데 참조변수 super와 this가 사용된다. 자손인 Child클래스에서의 super.x는 Parent에 선언된 인스턴스변수 x를 뜻하며, this.x또는 x는 Child클래스의 인스턴스변수 x를 뜻한다.
5.5 매개변수의 다형성

Product클래스는 Tv, Audio, Computer클래스의 조상이며, Buyer클래스는 제품(Product)을 구입하는 살마을 클래스로 표현한 것이다. Buyer클래스에 물건을 구입하는 기능의 메서드를 추가해보자. 구입할 대상이 필요하므로 매개변수로 구입할 제품을 넘겨받아야 한다. Tv를 살 수 있도록 매개변수를 Tv타입으로 하였다.
Buy클래스에 물건을 구입하는 기능의 메서드를 추가하면 구입할 대상이 필요하므로 매개변수로 구입할 제품을 넘겨받아야 한다.

이렇게 되면 제품의 종류가 늘어날 때마다 Buyer클래스에는 새로운 buy 메서드를 추가해주어야 할 것이다. 그러나 매개변수에 다형성을 적용하면 아래와 같이 하나의 메서드로 간단히 처리할 수 있다.

매개변수가 Product타입의 참조변수라는 것은, 메서드의 매개변수로 Product클래스의 자손타입의 참조변수면 어느 것이나 매개변수로 받아들일 수 있다는 뜻이다. 그리고 Product클래스에 rpice와 bonusPoint가 선언되어 있기 때문에 참조변수 p로 인스턴스의 price와 bonusPoint를 사용할 수 있기에 이와 같이 할 수 있다.
5.6 여러종류의 객체를 배열로 다루기
Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new AUdio();
↓
Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new AUdio();
이처럼 조상타입의 참조변수 배열을 사용하면, 공통의 조상을 가진 서로 다른 종류의 객체르 배열로 묶어서 다룰 수 있다. 또는 묶어서 다루고 싶은 객체들의 상속관계를 따져서 가장 가까운 공통조상 클래스 타입의 참조변수 배열을 생성해서 객체들을 저장하면 된다.


위 예제에서 Product배열로 구입한 제품들을 저장할 수 있도록 했지만 배열의 크기를 10으로 했기 대문에 11개 이상의 제품을 구입할 수는 없는 것이 문제다. 이런 경우 Vector클래스를 사용하면 된다. Vector클래스는 내부적으로 Object타입의 배열을 가지고 있어서, 이 배열에 객체를 추가하거나 제거할 수 있게 작성되어 있다. 그리고 배열의 크기를 알아서 관리해주기 때문에 신경쓰지 않아도 된다.
'Back-end > Java_T.I.L' 카테고리의 다른 글
| [TIL 220118] BufferedReader / BufferedWriter (0) | 2022.01.19 |
|---|---|
| [TIL 220111] 자바의정석 7. 객체지향 프로그래밍 II (3) (0) | 2022.01.12 |
| [TIL 220109] 자바의정석 7. 객체지향 프로그래밍 II (1) (0) | 2022.01.10 |
| [TIL 220108] 자바의정석 6. 객체지향 프로그래밍 I (2) (0) | 2022.01.08 |
| [TIL 220107] 자바의정석 6. 객체지향 프로그래밍 I (1) (0) | 2022.01.08 |
댓글