본문 바로가기
언어/자바의 정석

[자바의 정석] Chapter 7 객체 지향 프로그래밍II (7) - 추상클래스(abstract class)

by chan10 2021. 2. 16.

추상클래스(abstract class)

추상클래스란?

o  추상 클래스는 미완성 설계도로 비유할 수 있다. 미완성이라는 것은 멤버 개수에 관계된 것이 아닌 미완성 메서드(추상메서드)를 포함하고 있다는 의미이다.

o  추상클래스는 인스턴스를 생성할 수 없으며, 상속을 통해 자손클래스에 의해서만 완성될 수 있다. 추상클래스 자체로는 클래스로서 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어 중요한 의미를 갖는다.

o  키워드 ‘abstract’를 붙이기만 하면 추상클래스를 선언할 수 있다. 이를 통해 이 클래스에는 추상메서드가 있으니 상속을 통해 구현을 해줘야 한다는 것을 쉽게 알 수 있다.

abstract class Ex {...}

 

o  추상 클래스는 추상 메서드를 포함하고 있다는 것을 제외하면 일반 클래스와 다르지 않다. 생성자, 메서드, 매개 변수도 가질 수 있다.

 

추상메서드(abstract method)

o  추상메서드는 메서드의 선언부만 작성하고 구현부는 작성하지 않은 메서드이다. 메서드 내용이 상속받는 클래스에 따라 달라질 수 있기에 조상클래스에서는 선언부만 작성하고, 주석을 붙여 어떤 기능을 수행할 목적인지 알려준다. 실제 내용은 상속받는 클래스에서 구현하도록 비워 두는 것이다.

o  추상메서드 역시 키워드 ‘abstract’를 붙여주고 괄호 { }대신 세미콜론(;)을 붙여준다.

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

abstract class Player {    // 추상클래스
    abstract void play(int pos);    //추상 메서드
    abstract void stop();
}
 
class AudioPlayer extends Player {
    void play(int pos) { }    //상속받은 추상 메서드 구형
    void stop() { }
    
}
 
//추상 메서드를 하나라도 구현하지 않을 시 abstract class로 지정
abstract class AbstractPlayer extends Player{
    void play(int pos) { }
}

 

추상클래스의 작성

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

o  상속계층도를 따라 내려갈수록 클래스는 세분화되며, 올라갈수록 공통요소만 남게 된다.

추상[抽象]

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

추상화

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

구체화

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

 

abstract class Player {
    boolean pause;    //일시정지를 위한 변수
    int currentPos;    // 현재 play위치 저장 변수
    
    Player() {    //추상클래스도 생성자가 있어야 한다.
        pause = false;
        currentPos = 0;
    }
    
    /*지정된 위치에서 재생을 시작하는 기능이 수행하도록 작성*/
    abstract void play(int pos);
    /*재생을 즉시 멈추는 기능을 수행하도록 작성*/
    abstract void stop();
    
    void play() {
        play(currentPos);    // 추상메서드를 사용 할 수 있다.
    }
    void pauser() {
        if(pause) {    // pause 상태가 true이면 false로 변경 후 현재위치 play 실행
            pause = false;
            play(currentPos);
        } else {    // pause 상태가 true이면 true로 변경 후 play 중지
            pause = true;
            stop();
        }
    }
}
 
class CDPlayer extends Player {
    
    void play(int por) {/*조상의 추상메서드 구현, 내용생략*/}
    void stop() {/*조상의 추상메서드 구현, 내용생략*/}
    
    //CDPlayer에 추가 정으된 멤버
    int currentTrack;    // 현재 재생중인 트랙
    
    void nextTrack() {
        currentTrack++;
    }
    void preTrack() {
        if(currentTrack>1) {
            currentTrack--;
        }
    }
}

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

 

o  조상 클래스의 추상 메서드를 일반 메서드로 구현해서 자손 클래스에서 오버 라이딩하여 사용할 수도 있다. 굳이 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 changeMode() {/*공격모드로 변환*/}
}
 
class Dropship {
    int x, y;
    void move(int x, int y) {/* 지정 위치로 이동*/}
    void stop() {/*현재 위치에 정지*/}
    void load() {/*선택 대상을 태운다.*/}
    void unload() {/*선택 대상을 내린다.*/}
}
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 changeMode() {/*공격모드로 변환*/}
}
 
class Dropship extends Unit{
    void move(int x, int y) {/* 지정 위치로 이동*/}
    void load() {/*선택 대상을 태운다.*/}
    void unload() {/*선택 대상을 내린다.*/}
}

-       공통 부분을 뽑아내서 Unit클래스로 정의하고 상속받도록 했다. stop( )메서드는 공통이지만 이동 수단이 달라 move( )에서 다르게 정의할 것 이기에 제외하였다.

-       move 메서드가 추상 메서드로 선언됨에 따라 앞으로 Unit클래스를 상속받는 클래스는 move메서드를 반드시 구현해야 한다는 의미가 있다.

 

o  작성한 코드는 조상 클래스타입의 참조변수로 자손 클래스의 인스턴스를 참조하는 것이 가능하기에 조상 클래스 타입의 참조 배열로 담을 수 있다. 만약 공통된 조상 클래스가 없다면 하나의 배열로 다룰 수 없을 것이다.

o  move메서드는 추상 메서드라도 참조변수로 호출하는 것이 가능한데, 메서드는 참조변수 타입 관계없이 실제 인스턴스에 구현된 것이 호출되기 때문이다. , 조상 클래스(Unit)의 추상 메서드(move)를 호출하는 것이 아니라 실제 이 추상 메서드가 구현된 자손 클래스(Marine, Tank, Dropship)의 메서드가 호출되는 것이다.

Unit[] group = new Unit[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(100200);
    }
Object[] group = new Object[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메서드를 호출이 가능하다.

- 모든 클래스 조상인 Object 타입의 배열로도 서로 다른 종류의 인스턴스를 묶을 수 있다.

- 그러나 Object클래스에는 move메서드가 정의되어 있지 않기에 move메서드 호출 시 에러가 발생한다.