[JAVA] 객체 지향 프로그래밍 Ⅱ 정리 - (4) : 추상 클래스

DongGyu Jung·2022년 1월 26일
0

자바(JAVA)

목록 보기
15/60
post-thumbnail

🏃‍♂️ 들어가기 앞서..

본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕

*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.



📑 추상 클래스 (abstract class)

지금까지 우리가 사용했던 클래스
" 설계도 "라고 한다면

추상 클래스
" 미완성 설계도 "라고 생각하면 된다.

이 미완성의 의미는
" 미완성 메서드 (추상 메서드) 를 포함하고 있다. " 라는 뜻이다.

당연히 미완성이기 때문에
이 추상 클래스만으로는 인스턴스를 생성할 수 없다.
" 추상 클래스를 상속받은 자손클래스 "에 의해서만 완성될 수 있다.

당연히
이 추상 클래스 자체로는
본래의 클래스로서의 역할은 다하지 못하지만

새로운 클래스를 작성하는데 있어
" 바탕이 되는 조상 클래스 "로서 중요한 역할을 수행한다.

예를 들어
정말 여러가지 객체가 있는데
이 객체들이 사소한 차이점만 있고
대부분의 구성이 동일하다.

이 여러가지 객체를 모두 각각 설계하게 되면
상당히 비효율적일 것이다.

그런 경우
미완성 설계도(abstract class)바탕으로 만들어 놓고
이 설계도를 이용해서
각 객체의 설계도를 맞춤형으로 완성시키면 효율적인 작업이 될 것이다.

《사용 방법》

  • ' 추상메서드 '가 있다는 것 외에는
    일반 클래스와 다르지 않게 생성자/멤버변수/메서드 모두 가질 수 있다.
abstract class 클래스명 {
    ...
    ..
    .
}

🔖 추상 메서드 (abstract method)

메서드는 기본적으로
선언부구현부로 구성되어 있는 것을 다들 알 것이다.

여기서 추상 메서드는
" 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨둔 것 " 인 것이다.

이렇게 구현부만을 미완성 상태로 남겨놓으면
" 해당 추상메서드를 상속받은 클래스 "에서
맞춤형으로 구현할 수 있도록 할 수 이다.

주석으로 " 어떤 기능을 수행할 목적으로 작성되었는지 알려주는 것이 좋다. "

《사용 방법》

  • 추상클래스와 동일하게 앞에 abstract를 앞에 붙여주고
    구현부가 없기 때문에 괄호({}) 대신 ';'을 붙여준다.
abstract (Return타입) 메서드명() ;

추상클래스를 상속받는 자손클래스는
오버라이딩을 통해
조상인 추상클래스의 추상메서드를 모두 구현해주면 되고

단!
추상 클래스의 하나의 추상메서드라도 덜 완성시키게된다면
여전히 미완성인 것으로
에러가 발생하기 때문에

덜 완성시킬 경우에는
해당 클래스 앞엔 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) {
        ...
    }
}

그리고
다형성 특징이 있기때문에

자손클래스 타입 인스턴스
조상클래스 타입 참조변수에 대입하고
해당 변수를 통해 메서드를 호출해도 문제없다.

이유는
실제 가르키는 것은
조상 클래스의 추상 메서드를 호출하는 것이 아니라
" 추상메서드가 구현된 '인스턴스의 메서드' 를 호출되는 것 "이기 때문이다.
(단, 상속받은 자손클래스에서 추상메서드를 모두 완성시킨다는 가정 하에 위처럼 객체 생성 후 사용 가능)


📝 작성

" 공통된 성질 "을 뽑아 이를 일반적인 개념으로

상속 은 " 자손클래스에게 가진 것을 그대로 물려준다. "라면
추상화 는 " 자손클래스들에게 공통으로 필요한 공간을 만들어 놓은 것 "
이라고 생각하면 이해하기 쉽다.

상속은
단계가 내려갈수록 기능 추가 & 구체화가 특징이지만

추상화는 조금 색다르게
구체화와 추가보다는
다양한 클래스별 맞춤 메서드 작성이 특징이고
단계가 올라갈수록 상속받은 자손클래스들의 " 공통점 "을 알 수 있다

예제를 살펴보자.
우선 아래와 같이 각기 다른 클래스가 선언되어 있다고 치자.

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) ; // 해당 이름의 메서드를 사용하는 자손클래스는 많지만 각 클래스에서의 move메서드 실행 코드는 다르다.
    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[] group = new Unit[3] ;
group[0] = new Marine() ;
group[1] = new Tank() ;
group[2] = new Dropship() ;

for(int i = 0; i <group.length; i++)
    group[i].move(100,200);

중요한 것은
메서드를 호출시키면
조상 클래스의 추상 메서드를 호출하는 것이 아니라
" 추상메서드가 구현된 '인스턴스의 메서드' 가 호출되는 것 "이다.

(단, 최상위 클래스인 Object클래스 타입의 배열로 사용하면 에러 발생 _ 이 Object클래스에는 추상메서드가 없기 때문이다.)

Object[] group = new Object[3] ; //최상위 클래스인 Object클래스 타입으로 생성
group[0] = new Marine() ;
group[1] = new Tank() ;
group[2] = new Dropship() ;

for(int i = 0; i <group.length; i++)
    group[i].move(100,200); // Object클래스엔 move 메서드가 없기 때문에 에러발생

0개의 댓글