[Java] abstract

2020. 2. 12. 12:50 JAVA/Java

abstract


1.  추상화란

추상[抽象]: 낱낱의 구체적 표상이나 개념에서 공통된 성질을 뽑아 이를 일반적인 개념으로 파악하는 정신 작용

즉. 프로그래밍에서는 추상화는 기존의 클래스의 공통부분을 뽑아내서 조상클래스를 만드는 것이다.

추상화를 구체화와 반대되는 의미로 이해하면 보다 쉽게 이해할 수 있다. 상속계층도를 따라 내려갈수록 클래스는 점점 기능이 추가되어 구체와의 정도가 심해지며, 상속 계층도를 따라 올라갈수록 클래스는 추상화의 정도가 심해진다고 할 수 있다.

즉, 상속계층도를 따라 갈수록 세분화되며, 올라갈수록 공통요소만 남게 된다.

추상화: 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업

구체화: 상속을 통해 클래스를 구현, 확장하는 작업


2. 추상클래스

클래스를 설계도에 비유한다면 추상클래스는 미완성 설계도에 비유할 수 있다.

미완성 설계도란, 단어의 뜻 그대로 완성되지 못한 채로 남겨진 설계도를 말한다.

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

미완성 설계도로 완성된 제품을 만들 수 없듯이 추상클래스로 인스턴스는 생성할 수 없다.

추상클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있다.

추상클래스 자체로는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 

갖는다. 새로운 클래스르 작성할 때 아무 것도 없는 상태에서 시작하는 것보다는 완전하지는 못하더라도 어느 정도 틀을 갖는 상태에서 

시작하는 것이 나을 것이다.

실생활에서 예를 들자면, 같은 크기의 TV라도 기능의 차이에 따라 여러 종류의 모델이 있지만, 사실 이 들의 설계도는 아마 90%정도는

동일할 것이다. 서로 다른 세 개의 설계도를 따로 그리는 것보다는 이들의 공통부분만을 그린 미완성 설계도를 만들어 놓고, 이 미완성 설계도

를 이용해서 각각의 설계도를 완성하는것이 훨씬 효율적이다.

추상클래스는 키워드 'abstract'를 붙이기만 하면 된다. 이렇게 함으로써 이 클래스를 사용할 때, 클래스 선언부의 abstract를 보고 이 

클래스에는 추상메서드가 있으니 상속을 통해서 구현해주어야 한다는 것을 쉽게 알 수 있을 것이다.

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

멤버변수와 메서드도 가질 수 있다.

cf.) 추상메서드를 포함하고 있지 않은 클래스에도 키워드 'abstract'를 붙여서 추상클래스로 지정할 수도 있다. 추상 메서드가 없는 

완성된 클래스라 할지라도 추상클래스로 지정되면 클래스의 인스턴스를 생성할 수 없다.


3. 추상 메서드

메서드는 선언부와 구현부(몸통)로 구성되어 있다고 했다. 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드이다.

즉 설계만 해 놓고 수행될 내용은 작성하지 않았기 때문에 미완성 메서드인 것이다.

메서드를 이와 같이 미완성 상태로 남겨 놓는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서는

선언부만을 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려 주고, 실제 내용은 상속받는 클래스에서 구현하도록

비워 두는 것이다. 그래서 추상클래스를 상속받는 자손 클래스는 조상의 추상메서드를 상황에 맞게 적절히 구현해주어야 한다.

추상메서드 역시 키워드 'abstract'를 앞에 붙여 주고, 추상메서드는 구현부가 없으므로 괄호{}대신 문장의 끝을 알리는 ';'을 적어준다.

추상클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상클래스의 추상메서드를 모두 구현해주어야 한다.

만일 조상으로부터 상속받은 추상메서드 중 하나라도 구현하지 않는다면, 자손클래스 역시 추상클래스로 지정해 주어야 한다.

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){.....}//추상 메서드를 구현

}

실제 작업내용인 구현부가 없는 메서드가 무슨 의미가 있을까 싶기도 하겠지만, 메서드를 작성할 때 실제 작업내용인 구현부보다 더 중요한 부분이 선언부이다.

메서드의 이름과 메서드의 작업에 필요한 매개변수, 그리고 작업의 결과로 어떤 타입의 값을 반환할 것인가를 결정하는 것은 쉽지 않은 일이다.

선언부만 작성해도 메서드의 절반 이상이 완성된 것이라 해도 과언이 아니다.

메서드를 사용하는 쪽에서 메서드가 실제로 어떻게 구현되어있는지 몰라도 메서드의 이름과 매개변수, 리턴타입, 즉 선언부만 알고 있으면 되므로 내용이 없을 지라도

추상메서드를 사용하는 코드를 작성하는 것이 가능하며 실제로는 자손클래스에 구현된 완성된 메서드가 호출되도록 할 수 있다.


4. 추상클래스의 작성

아래에 Player라는 추상클래스를 작성해 보았다. 이 클래스는 VCR이나 Audio와 같은 재생 가능한 기기(Player)를 클래스로 작설할 때, 이 들의 조상클래스로 사용될 수 있을 것이다.

abstract class Player{

boolean pause;// 일시정지 상태를 저장하기 위한 변수

int currentPos;// 현재 play되고 있는 위치를 저장하기 위한 변수

Player(){// 추상클래스도 생성자가 있어야 한다.

pause= false;

currentPos = 0;

}// 저장된 위치(pos)에서 재생을 시작하는 기능이 수행하도록 작성되어야 한다.


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

  // 재생을 즉시 멈추는 기능을 수행하도록 작성되어야 한다.

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

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(){

.... 실제 구현 내용

}

// CDPlayer 클래스에 추가로 정의된 멤버


int currentTrack;// 현재 재생중인 트랙

void nextTrack(){

currentTrack++;

....

}

void preTrack(){

if(currentTrack >1){

currentTrack--;

}

....

}

}


class CDPlayer extends Player{

// 조상 클래스의 추상메서드를 CDPlayer클래스의 기능에 맞게 완성해주고, CDPlayer만의 새로운 기능들을 추가하였다.

사실 Player클래스의 play(int pos)와 stop()을 추상메서드로 하는 대신, 아무 내용도 없는 메서드로 작성할 수도 있다. 아무런 내용도 없이 단지 괄호{}만 있어도,

추상메서드가 아닌 일반메서드로 간주되기 때문이다.


class Player{

...

void play(int pos){ }

void stop(){ }

...

}

어차피 자손 클래스에서 오버라이딩하여 자신의 클래스에 맞게 구현할 테니 추상메서드로 선언하는 것과 내용없는 빈 몸통만 만들어 놓는 것이나 별 차이가 없어 보인다.

그래도 굳이 abstract를 붙여서 추상메서드로 선언하는 이유는 자손클래스에서 추상메서드를 반드시 구현하도록 강요하기 위해서이다.

만일 추상메서드로 정의되어 있지 않고 위와 같이 빈 몸통만 가지도록 정의되어 있다면, 상속받는 자손 클래스에서는 이 메서드들이 온전히 구현된 것으로 인식하고 오버라이딩을 통해 자신의 클래스에 맞도록 구현하지 않을 수도 있기 때문이다.

하지만, abstract를 이용해서 추상메서드로 정의해 놓으면, 자손 클래스를 작성할 때 이들이 추상메서드이므로 내용을 구현해주어야 한다는 사실을 인식하고 자신의 클래스에 알맞게 구현할 것이다.


이번엔 기존의 클래스로부터 공통된 부분을 뽑아내어 추상클래스를 만들어 보도록 하자.

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 unloas{   }//선택된 대상을 내린다.

}


이 유닛들은 각자 나름대로 기능을 가지고 있지만 공통부분만 뽑아내어 하나의 클래스로 만들고, 이 클래스로부터 상속받도록 변경해보자.

abstract class Unit{

int x,y;

abstract void move(int x, int y);

void stop(){//현재 위치에 정지

}


class Marine extends Unit{

void move(int x, int y){   }//지정된 위치로 이동

void stimPack(){   }// 스팀팩을 사용한다.

}


class Tank extends Unit{

void move(int x, int y){   }//지정된 위치로 이동

void changMode(){   }//공격 모드로 전환한다.

}


class Dropship extends Unit{

void move(int x, int y){   }//지정된 위치로 이동

void load(){   }// 선택된 대상을 태운다.

void unloas{   }//선택된 대상을 내린다.

}

이들 클래스에 대해서 stop메서드는 선언부와 구현부 모두 공통적이지만, Marine, Tank는 지상유닛이고 Dropship은 공중유닛이기 때문에 이동하는 방법이 서로 달라서 move메서드의 실제 구현 내용이 다를 것이다.

그래도 move메서드의 선언부는 같기 때문에 추상메서드로 정의할 수 있다. 최대한의 공통부분을 뽑아내기 위한 것이기도 하지만. 모든 유닛은 이동할 수 있어야 하므로 Unit클래스에는 move메서드가 반드시 필요한 것이기 때문이다.

move 메서드가 추상메서드로 선언된 것에는,  앞으로 Unit 클래스를 상속받아서 작성되는 클래스는 move 메서드를 자신의 클래스에 알맞게 반드시 구현해야 한다는 의미가 담겨 있는 것이기도 하다.


Unit[ ] group = new group[4];

group[0] = new Marine();

group[1] = new Tank();

group[2] = new Marine();

group[3] = new Dropship();


for(int i = 0; i <group.length; i++){

group[i].move(100,200);

}

위의 코드는 공통적인 Unit클래스 타입의 참조변수 배열을 통해서 서로 다른 종류의 인스턴스를 하나의 묶음으로 다룰 수 있다는 것을 보여 주기 위한 것이다. 다형성에서 배웠듯이 조상 클래스타입의 참조변수로 자손 클래스의 인스턴스를 참조하는 것이 가능하기 때문에 이처럼 조상 클래스타입의 배열에 자손 클래스의 인스턴스를 담을 수 있는 것이다.

만일 이들 클래스간의 공통 조상이 없다면 이처럼 하나의 배열로 다룰 수 없을 것이다. Unit클래스에 move메서드를 호출하는 것이 가능하다. 메서드는 참조변수의 타입에 관계없이 실제 인스턴스에 구현된 것이 호출되기 때문이다.

group[i].move(100,200)과 같이 호출하는 것이 Unit 클래스의 추상메서드인 move를 호출하는 것 같이 보이지만 실제로는 이 추상메서드가 구현된 Marine, Tank, Dropship인스턴스의 메서드가 호출되는 것이다.

모든 클래스의 조상인 Object클래스 타입의 배열로도 서로 다른 종류의 인스턴스를 하나의 묶음으로 다룰 수 있지만, Object클래스에는 move메서드가 정의되어 있지 않기 때문에 move메서드를 호출하는 부분에서 에러가 발생한다.


5. 인터페이스와 비교

인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통만 갖춘 일반메서드 또는 멤버변수를 구성원으로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다름 어떠한 요소도 허용하지 않는다.

추상클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아무것도 없고 밑그림만 그려져 있는 '기본 설계도'라고 할 수 있다.

- 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.

- 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.


 추상클래스

인터페이스 

 추상메서드+일반메서드, 멤버변수+상수

 추상메서드, 상수



출처: https://devbox.tistory.com/entry/Java-abstract?category=574549 [장인개발자를 꿈꾸는 :: 기록하는 공간]

'JAVA > Java' 카테고리의 다른 글

[Java] static  (0) 2020.02.12
[Java] 클래스 메서드와 인스턴스 메서드  (0) 2020.02.12
[Java] 클래스멤버와 인스턴스멤버간의 참조와 호출  (0) 2020.02.12
[Java] 변수의 종류  (0) 2020.02.12
[Java] 인터페이스의 이해  (0) 2020.02.12
[Java] 형변환  (0) 2020.02.12
[Java] 실행  (0) 2020.02.12
[Java] Java SE, JDK, JRE  (0) 2020.02.12