🏃♂️ 들어가기 앞서..
본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 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 메서드가 없기 때문에 에러발생