본문 바로가기

Java/기초

[Java] 객체지향 프로그래밍 4

1. 제어자(modifier)

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

- 접근 제어자 : public, protected, default, private

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

 

제어자는 클래스나 멤버변수와 메서드에 주로 사용되며, 하나의 대상에 대해서 여러 제어자를 조합하여 사용하는 것이 가능하다.

단, 접근 제어자는 한 번에 네 가지 중 하나만 선택해서 사용할 수 있다. 

[참고] 제어자들 간의 순서는 관계없지만 주로 접근 제어자를 제일 왼쪽에 놓는 경향이 있다.

 

1.1 static - 클래스의, 공통적인

인스턴스변수는 하나의 클래스로부터 생성되었더라고 각기 다른 값을 유지하지만, 클래스변수(static멤버변수)는 인스턴스에 관계없이 같은 값을 갖는다. 그 이유는 하나의 변수를 모든 인스턴스가 공유하기 때문이다.

static이 붙은 멤버변수와 메서드, 그리고 초기화 블럭은 인스턴스가 아닌 클래스에 관계된 것이기 때문에 인스턴스를 생성하지 않고도 사용할 수 있다.

* static이 사용될 수 있는 곳 - 멤버변수(전역변수), 메서드, 초기화 블럭

제어자 대상 의미
static 멤버변수 - 모든 인스턴스에 공통적으로 사용되는 클래스변수가 된다.
- 클래스변수는 인스턴스를 생성하지 않고도 사용 가능하다.
- 클래스가 메모리에 로드될 때 생성된다.
메서드 - 인스턴스를 생성하지 않고도 호출이 가능한 static메서드가 된다.
- static메서드 내에서 인스턴스멤버들을 직접 사용할 수 없다.

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

변수에 사용되면 값을 변경할 수 없는 상수가 되며, 메서드에 사용되면 오버라이딩을 할 수 없게 되고 클래스에 사용되면 자신을 확장하는 자손클래스를 정의하지 못하게 된다.

* final이 사용될 수 있는 곳 - 클래스, 메서드, 멤버변수, 지역변수

제어자 대상 의미
final 클래스 변경될 수 없는 클래스, 확장될 수 없는 클래스가 된다.
그래서 final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다.
메서드 변경될 수 없는 메서드, final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.
멤버변수 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다.
지역변수

1.3 abstract - 추상의, 미완성의

메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언하는데 사용된다.

* abstract가 사용될 수 있는 곳 - 클래스, 메서드

제어자 대상 의미
abstract 클래스 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.
메서드 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.

추상 클래스는 아직 완성되지 않은 메서드가 존재하는 '미완성 설계도'이므로 인스턴스를 생성할 수 없다.

1.4 접근 제어자(accss modifier)

접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다.

접근제어자가 default임을 알리기 위해 실제로 default를 붙이지는 않는다.

* 접근 제어자가 사용될 수 있는 곳 - 클래스, 멤버변수, 메서드, 생성자

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

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

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

- public : 접근 제한이 전혀 없다.

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

대상 사용가능한 접근 제어자
클래스 public, (default)
메서드 public, preotected, (default), private
멤버변수
지역변수 없 음

* 접근 제어자를 사용하는 이유(캡슐화)

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

- 외부에는 불필요한, 내부적으로만 사용되는, 부분을 감추기 위해서

* 생성자의 접근 제어자

생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다. 생성자의 접근 제어자를 private으로 지정하면, 외부에서 생성자에 접근할 수 없으므로 인스턴스를 생성할 수 없게 된다. 그래도 내부에서 인스턴스 생성이 가능하다.

1.5 제어자(modifier)의 조합(정리)

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

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

* 제어자를 조합해서 사용할 때 주의사항

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

- static 메서드는 몸통이 있는 메서드만 사용할 수 있기 때문이다.

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

- 클래스에 사용되는 final은 클래스에 확장할 수 없다는 의미이고 absctract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문이다.

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

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

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

- 접근 제어자가 private인 메서드는 오버라이딩 될 수 없기 떄문이다. 이 둘 중 하나만 사용해도 의미가 충분하다. 

 

2. 다형성(polymorphism)

조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 하였다.

반대로 자손타입의 참조변수로 조상타입의 인스턴스를 참조할 수는 없다.

2.1 참조변수의 형변환

- 자손타입 -> 조상타입 (Up-casting) : 현변환 생략가능

- 자손타입 <- 조상타입 (Down-casting) : 형변환 생략불가

Car클래스는 FireEngine클래스와 Ambulance클래스의 조상이다. 그렇다고 해서 FireEngine클래스와 Ambulance클래스가 형제관계는 아니다. 잡에서는 조상과 자식관계만 존재하기 때문에 FireEngine클래스와 Ambulance클래스는 서로 아무런 관계가 없다.

캐스트 연산자를 이용해서 조상타입의 참조변수를 자손타입의 참조변수로 형변환한 것이기 때문에 문제가 없어 보이지만, car가 참조하고 있는 인스턴스가 Car타입의 인스턴스라는데 있다. 조상타입의 인스턴스를 자손타입의 참조변수로 참조하는 것은 허용되지 않는다.

서로 상속관계에 있는 클래스 타입의 참조변수간의 형변환은 양방향으로 자유롭게 수행될 수 있으나, 참조변수가 참조하고 있는 인스턴스의 자손타입으로 형변환을 하는 것은 허용되지 않는다. 참조변수가 가리키는 인스턴스의 타입이 무엇인지 확인하는 것이 중요하다.

2.2 instanceof 연산자

참조변수가 참조하고 있는 인스턴스의 실제 타입을 알아보기 위해 instanceof 연산자를 사용한다.

어떤 타입에 대한 instanceof연산의 결과가 true라는 것은 검사한 타입으로 형변환이 가능하다는 것을 뜻한다.

2.3 참조변수와 인스턴스의 연결

조상 타입의 참조변수와 자손 타입의 참조변수의 차이점이 사용할 수 있는 멤버의 개수에 있다고 배웠다. 여기서 한 가지 더 알아두어야 할 내용이 있다.

조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자손 클래스에 중복으로 정의했을 때, 조상타입의 참조변수로 자손 인스턴스를 참조하는 경우와 자손타입의 참조 변수로 자손 인스턴스를 참조하는 경우는 서로 다른 결과를 얻는다.

 메서드의 경우 조상 클래스의 메서드를 자손의 클래스에서 오버라이딩한 경우에도 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출되지만, 멤버변수의 경우 참조변수의 타입에 따라 달라진다.

[참고] static메서드는 static변수처럼 참조변수의 타입에 영향을 받는다. 참조변수의 타입에 영향을 받지 않는 것은 인스턴스메서드 뿐이다. 그래서 static메서드는 반드시 참조변수가 아닌 '클래스이름.메서드()'로 호출해야 한다.

멤버변수가 조상 클래스와 자손 클래스에 중복으로 정의된 경우, 조상타입의 참조변수를 사용했을 때는 조상 클래스에 선언된 멤버변수가 사용되고, 자손타입의 참조변수를 사용했을 때는 자손 클래스에 선언된 멤버변수가 사용된다.

타입은 다르지만, 참조변수 p와 c 모두 Child인스턴스를 참조하고 있다. 그리고 Parent클래스와 Child클래스는 서로 같은 멤버들을 정의하고 있다.

이 때 조상타입의 참조변수 p로 Child인스턴스의 멤버들을 사용하는 것과 자손타입의 참조변수 c로 Child인스턴스의 멤버들을 사용하는 것의 차이를 알 수 있다.

메서드인 method()의 경우 참조변수의 타입에 관계없이 항상 실제 인스턴스의 타입인 Child클래스에 정의된 메서드가 호출되지만, 인스턴스변수인 x는 참조변수의 타입에 따라 달라진다.

이전의 예제와는 달리 Child클래스에는 아무런 멤버도 정의되어 있지 않고 단순히 조상으로부터 멤버들을 상속받는다. 그렇기 때문에 참조변수의 타입에 관계없이 조상의 멤버들을 사용하게 된다.

이처럼 자손 클래스에서 조상 클래스의 멤버를 중복으로 정의하지 않았을 때는 참조변수의 타입에 다른 변화는 없다. 어느 클래스의 멤버가 호출되어야 할지, 즉 조상의 멤버가 호출되어야할 지, 자손의 멤버가 호출되어야 할지에 대해 선택의 여지가 없기 때문이다.

참조변수의 타입에 따라 결과가 달라지는 경우는 조상 클래스의 멤버변수와 같은 이름의 멤버변수를 자손 클래스에 중복해서 정의한 경우뿐이다.

자손 클래스 Child에 선언된 인스턴스변수 x와 조상 클래스 Parent로부터 상속받은 인스턴스변수 x를 구분하는데 참조변수 super와 this가 사용된다. 자손인 Child클래스에서의 super.x는 조상 클래스인 Parent에 선언된 인스턴스변수 x를 뜻하며, this.x 또는 x는 Child클래스의 인스턴스변수 x를 뜻한다. 그래서 위 결과에서 x와 this.x의 값이 같다.

멤버변수들은 주로 private으로 접근을 제한하고, 외부에서는 메서드를 통해서만 멤버변수에 접근할 수 있드록 한다. 이번 예제에서처럼 다른 외부 클래스에서 참조변수를 통해 직접적으로 인스턴스변수에 접근할 수 있게 하지 않는다.

예제에서 알 수 있듯이 인스턴스변수에 직접 접근하면, 참조변수의 타입에 따라 사용되는 인스턴스변수가 달라질 수 있으므로 주의해야 한다.

 

 

'Java > 기초' 카테고리의 다른 글

[Java] 예외처리  (0) 2020.08.30
[Java] 객체지향 프로그래밍 5  (0) 2020.08.30
[Java] 객체지향 프로그래밍 3  (0) 2020.07.12
[Java] 객체지향 프로그래밍 2  (0) 2020.07.08
[Java] 객체지향 프로그래밍 1  (0) 2020.06.24