본문 바로가기

개발 관련 지식/자바(Java)

[자바] 추상 클래스(abstract class)

* 추상클래스(abstract class)

: 클래스를 설계도에 비유한다면, 추상클래스는 미완성 설계도에 비유할 수 있다. 미완성 설계도란, 단어의 뜻 그대로 완성되지 못한 채로 남겨진 설계도를 말한다.

클래스가 미완성이라는 것은 멤버의 개수에 관계된 것이 아니라, 단지 미완성 메서드(추상메서드)를 포함하고 있다는 의미이다.

미완성 설계도로 완성된 제품을 만들 수 없듯이 추상클래스로 인스턴스는 생성할 수 없다. 추상클래스는 상속을 통해 자손클래스에 의해서만 완성될 수 있다.

추상클래스 자체로는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 갖는다. 새로운 클래스를 작성할 때 아무 것도 없는 상태에서 시작하는 것보다는 완전하지는 못하더라도 어느 정도 틀을 갖춘 상태에서 시작하는 것이 나을 것이다.

실생활의 예를 들자면, 같은 크기의 TV라도 기능의 차이에 따라 여러 종류의 모델이 있지만, 사실 이 들의 설계도는 아마 90%정도는 동일할 것이다. 서로 다른 세 계의 설계도를 따로 그리는 것보다는 이들의 공통부분만을 그린 미완성 설계도를 만들어 놓고, 이 미완성 설계도를 이용해서 각각의 설계도를 완성하는 것이 훨씬 효율적일 것이다.

추상클래스는 키워드 'abstract'를 붙이기만 하면 된다. 이렇게 함으로써 이 클래스를 사용할 때, 클래스 선언부의 abstract를 보고 이 클래스에는 추상메서드가 있으니 상속을 통해서 구현해주어야 한다는 것을 쉽게 알 수 있을 것이다.

 

<예문>

abstract class 클래스이름{

// ...

}

 

추상클래스는 추상메서드를 포함하고 있다는 것을 제외하고는 일반클래스와 전혀 다르지 않다. 추상클래스에도 생성자가 있으며, 멤버변수와 메서드도 가질 수 있다.

 

 

[참고] 추상메서드를 포함하고 있지 않은 클래스에도 키워드 'abstract'를 붙여서 추상클래스로 지정할 수 도 있다. 추상메서드가 없는 완성된 클래스라 할지라도 추상클래스로 지정되면 클래스의 인스턴스를 생성할 수 없다.

 

 

* 추상메서드(abstract method)

: 메서드는 선언부와 구현부(몸통)로 구성되어 있다. 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드이다. 즉, 설계만 해 놓고 실제 수행될 내용은 작성하지 않았기 때문에 미완성 메서드인 것이다.

메서드를 이와 같이 미완성 상태로 남겨 놓는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서는 선언부만을 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려 주고, 실제 내용은 상속받은 클래스에서 구현하도록 비워 두는 것이다. 그래서 추상클래스를 상속받는 자손 클래스는 조상의 추상메서드를 상황에 맞게 적절히 구현해주어야 한다.

 

<예문>

abstract class Player { // 추상 클래스

abstract void play(int pos);    // 추상 메서드

abstract void stop();             // 추상 메서드

}


class AudioPlayer extends Player{

void play(int pos) { /* 내용 생략 */ }   // 추상 메서드를 구현

void stop() { /* 내용 생략 */ }            // 추상 메서드를 구현

}


abstract class AbstractPlayer extends Player{

void play(int pos) { /* 내용 생략 */ }   // 추상 메서드를 구현

}

 

 

 추상클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상클래스의 추상메서드를 모두 구현해주어야 한다. 만일 조상으로부터 상속받은 추상메서드 중 하나라도 구현하지 않는다면, 자손클래스 역시 추상클래스로 지정해 주어야 한다.

 

* 추상클래스의 작성

: 상속이 자손 클래스를 만드는데 조상 클래스를 사용하는 것이라면, 이와 반대로 추상화는 기존의 클래스의 공통 부분을 뽑아내서 조상 클래스를 만드는 것이라고 할 수 있다.

 

<예문>

abstract class Player { // 추상 클래스

 boolean pause;  // 일시정지 상태를 저장하기 위한 변수
 int currentPos;  // 현재 play되고 있는 위치를 저장하기 위한 변수
 
 Player(){   // 추상 클래스도 생성자가 있어야 한다.
  pause = false;
  currentPos = 0;
 }
 // 지정된 위치(pos)에서 재생을 시작하는 기능이 수행하도록 작성되어야 한다.
 abstract void play(int pos); // 추상 메서드
 // 재성을 즉시 멈추는 기능을 수행하도록 작성되어야 한다.
 abstract void stop(); // 추상 메서드

 void play(){
  play(currentPos); //추상메서드를 사용할 수 있다.
 }
 
 void pause(){
  if(pause){    //pause가 true일 때(정지상태) pause가 호출되면
   pause = false;  //pause의 상태를 false로 바꾸고
   play(currentPos); //현재의 위치에서 play를 한다.
  } else{     //pause가 false일 때(play상태) pause가 호출되면
   pause = true;  //pause의 상태를 true로 바꾸고
   stop();    //play를 멈춘다.
  }
 }
}

class CDPlayer extends Player{
 //조상 클래스의 추상메서드를 구현한다.
 void play(int currentPos){
 }
 
 void stop(){
 }
 
 // CDRPlayer 클래스에 추가로 정의된 멤버
 int currentTrack; //현재 재생 중인 트랙
 
 void nextTrack(){
  currentTrack++;
  // ...
 }
 
 void preTrack(){
  if(currentTrack > 1){
   currentTrack--;
  }
  // ...
 }

 

기존의 클래스로부터 공통된 부분을 뽑아내어 추상클래스를 만들어보는 예문을 살펴보자.

 

<예문1>

class Marine{ //보병
 int x, y;
 void move(int x, int y){/* 지정된 위치로 이동 */}
 void stop(){/* 현재 위치에 정지 */}
 void stimPack(){/* 스팀팩을 사용한다 */}
}

class Tank{  //탱크
 int x, y;
 void move(int x, int y){/* 지정된 위치로 이동 */}
 void stop(){/* 현재 위치에 정지 */}
 void changMode(){/* 공격모드를 변환한다. */}
}

class DropShip{ //수송선
 int x, y;
 void move(int x, int y){/* 지정된 위치로 이동 */}
 void stop(){/* 현재 위치에 정지 */}
 void load(){/* 선택된 대상을 태운다. */}
 void unload(){/* 선택된 대상을 내린다. */}
}

 

 

위의 예문1에서는 각자의 메서드들을 가지고 있지만 공통부분을 뽑아내어 하나의 클래스로 만들고, 이 클래스로부터 상속받도록 변경해보자.

 

<예문2>

abstract class Unit{
 int x, y;
 abstract void move(int x, int y);
 void stop(){/* 현재 위치에 정지 */};
}

class Marine1{ //보병
 void move(int x, int y){/* 지정된 위치로 이동 */}
 void stimPack(){/* 스팀팩을 사용한다 */}
}

class Tank1{  //탱크
 void move(int x, int y){/* 지정된 위치로 이동 */}
 void changMode(){/* 공격모드를 변환한다. */}
}

class DropShip1{ //수송선
 void move(int x, int y){/* 지정된 위치로 이동 */}
 void load(){/* 선택된 대상을 태운다. */}
 void unload(){/* 선택된 대상을 내린다. */}
}

 

 

 위의 예문2와 같이 각 클래스의 공통부분을 뽑아내서 Unit 클래스를 정의하고 이로부터 상속받도록 하였다. 이 Unit 클래스는 다른 유닛을 위한 클래스를 작성하는데 재활용 될 수 있다.