Abstract Class
추상클래스
추상클래스는 일반클래스와 약간 다르다.
일반적인 클래스는 구체적으로 데이터를 담아 인스턴스화 하여 직접 다루는 클래스이다.
반대로 추상클래스는 추상적인 데이터를 담고 있는 클래스이다. 따라서 일반클래스와 다르게
추상클래스는 인스턴스화가 불가능하다.
Abstract Method를 선언하여 상속을 통해서 자손 클래스에서 완성하도록 한다.
미완성 설계도 라고 표현한다.
- 하위 클래스들의 공통점들을 모아 추상화하여 만든 클래스
- 다중 상속이 불가능하여 단일 상속만 허용
- 추상 메소드 외에 일반클래스와 같이 일반적인 필드, 메서드, 생성자를 가질 수 있음
- 추상화를 하면서 중복되는 클래스 멤버들을 통합 및 확장 가능
- 인터페이스와 다른점은, 클래스간의 연관 관계를 구축하는 것에 초점을 가짐
사용이유
- 클래스에
추상화
개념을 접목 시켜 보다 구조적으로 객체를 설계 - 프로그램의 유지보수성 상승
- 개별 프로젝트 보다는 범용 라이브러리나 프레임워크 시스템을 설계하는데 유용하게 사용
만일 프로그램에 어떠한 기능을 업그레이드 한다고 하면
수정/추가에 대해 유연적이게 해주어, 퀄리티 높은 프로그램과 솔루션을 개발할 수 있게 해준다.
기본 문법
자바에서는 abstract
키워드를 클래스명과 메서드명 옆에 붙임으로서
컴파일러에게 추상클래스와 추상메서드임을 알려주게 된다.
추상 메서드는 작동 로직이 없고 이름만 있는 껍데기 메서드이다.
즉, 메서드의 선언부만 작성하고 구현부는 미완성인 채로 남겨둔 메소드이다.
abstract class ClassName{
private String field;
public Test(String field){
/* Method */
}
public abstract void MethodName();
}
추상 클래스 안의 메서드를 미완성으로 남겨놓는 이유
- 추상 클래스를 상속받는 자식 클래스의 주제에 따라서 상속 받는 메서드의 내용이 달라질 수 있기때문
- 추상 클래스에서 메서드를 선언부만 작성하고, 실제 내용은 상속받는 클래스에서 구현하도록 일부러 비워두는 개념
- 따라서 추상 클래스를 상속받는 자식 클래스는 부모의 추상 메서드를 상황에 맞게 적절히 재정의 하여 구현해야 함
- 클래스 선언부의
abstract
키워드가 있는것은 추상메서드가 있으니 상속을 통해서 구현하라는 의미
추상클래스는 추상 메서드를 포함하고 있다는것을 제외하고 일반클래스와 다르지 않다
- 추상클래스에도 생성자가 존재
- 독립적인 인스턴스 멤버 변수와 메서드를 가질 수 있음
// 추상 클래스
abstract class Human {
abstract public void walk(); // 추상 메소드
abstract public void eat(); // 추상 메소드
public int health; // 인스턴스 필드
public void run() { // 인스턴스 메소드
System.out.println("run run");
}
}
추상클래스 생성자
new
생성자를 통해 인스턴스 객체를 직접 만들 수 없음- 상속 구조에서 부모 클래스를 나타내는 역할로만 이용됨
- 따라서 반드시 추상 클래스를 자식 클래스에 상속 시키고, 자식 클래스를 인스턴스화 하여 사용
abstract class Animal { //추상 클래스
}
class Cat extends Animal { // 추상 클래스 상속
}
class Dog extends Animal {
}
public class Main {
public static void main(String[] args) {
// 추상 클래스를 상속한 자식 클래스를 객체로 초기화
Cat c = new Cat();
Dog d = new Dog();
}
}
super()
메소드를 이용해 추상 클래스 생성자 호출 가능- 추상클래스를 상속한 자식클래스를
new
생성자로 객체를 초기화 하면, 자식클래스 생성자 메서드 내에서 가장 먼저 부모 클래스인 추상 클래스의 생성자가 실행됨 - 만일 부모 추상 클래스 생성자 실행에 있어 인자를 주어 제어하고 싶다면, 자식 클래스 생성사 메서드 내에서
super()
부모 생성자 호출 메서드를 통해 가능
추상 클래스 활용
- 추상클래스는 새로운 클래스를 작성하는데 있어 바탕이 되는 부모 클래스로서 중요한 의미를 가짐
- 추상클래스를 활용하면 객체 지향 프로그래밍에서 중요한 특징인 다형성을 가지는 메서드의 집합을 정의할 수 있음
공통멤버의 통합으로 중복제거
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 siegeMode() {} // 고유 능력 시즈 모드 사용
}
class DropShip {
int x, y;
void move(int x, int y) {} // 지정된 위치로 이동
void stop() {} // 현재 위치에 정지
void loadUnload() {} // 고유 능력 탑승 사용
}
위 코드를 보면 Marine, Tank, Dropship 클래스가 정의 되어있다.
클래스의 멤버들을 살펴보면 사용처와 이름이 겹치는 필드와 메서드가 있다.
- 상속 기능을 이용해 3개의 클래스를 대표할 수 있는 부모 추상 클래스로 묶음
- 상위 클래스의 특징을 하위클래스에서 그대로 물려 받아 사용할 수 있는 상속 특징을 이용
- 코드의 중복 제거, 코드 재사용성 증대 효과
- 즉, 자주 사용될 것이 예상되는 기능을 모아놓은 추상 클래스를 만들고 재사용하여 유지관리 효율화 추구
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) {
System.out.println("걸어서 이동");
}
void stimPack() {} // 고유 능력 스팀팩 사용
}
class Tank extends Unit{
void move(int x, int y) {
System.out.println("굴러서 이동");
}
void siegeMode() {} // 고유 능력 시즈 모드 사용
}
class DropShip extends Unit{
void move(int x, int y) {
System.out.println("날아서 이동");
}
void loadUnload() {} // 고유 능력 탑승 사용
}
생각해보면 추상클래스가 아니더라도 일반클래스를 사용하여 상속해도 된다.
하지만 추상클래스를 사용하는 이유는 무엇일까?
구현의 강제성을 통한 기능 보장
위의 자식 클래스들을 다음 메인 메소드에서 인스턴스화 하여 사용한다고 하자.
public class Test1 {
public static void main(String[] args) {
Unit[] group = new Unit[3];
group[0] = new Marine();
group[1] = new Tank();
group[2] = new DropShip();
for(Unit u : group) {
u.move(100, 200);
}
}
}
- 각 자식들을 Unit 추상클래스 타입으로 묶었으니, 업캐스팅을 통해 Unit 배열로 객체들 할당
- for문을 통해 배열을 순회하여
move()
메소드 실행하도록 다형성을 이용
스펙이 추가된 상황, Battlecruiser 클래스를 추가하여 Unit 부모 클래스에 상속
abstract class Unit {
int x, y;
abstract void move(int x, int y); // 지정된 위치로 이동
void stop() {} // 현재 위치에 정지
}
class Battlecruiser extends Unit {
void yamato() {} // 고유 능력 야마토 사용
}
class Marine extends Unit { ... }
class Tank extends Unit { ... }
class DropShip extends Unit { ... }
추상 클래스의 추상 메소드를 구현하지 않는다면?
- 에러가 발생한다, 추상 클래스의 추상 메소드를 반드시 구현하여야 하기 때문
- 구현을 강제함으로써 중요한 기능을 보장한다.
만약 일반 클래스로 상속 관계를 맺는 경우에는?
- 에러가 발생하지 않는다.
- 일반 클래스의 메서드를 오버라이딩의 여부는 상관없기 때문
- 만약 공통적으로 사용하는 메서드를 오버라이딩 되지 않고 그래도 사용된다면, 에러발생
class Unit {
int x, y;
void move(int x, int y); // 지정된 위치로 이동
void stop() {} // 현재 위치에 정지
}
class Battlecruiser extends Unit {
void yamato() {} // 고유 능력 야마토 사용
}
class Marine extends Unit { ... }
class Tank extends Unit { ... }
class DropShip extends Unit { ... }
public class Test1 {
public static void main(String[] args) {
Unit[] group = new Unit[3];
group[0] = new Marine();
group[1] = new Tank();
group[2] = new DropShip();
gruop[3] = new Battlecruiser();
for(Unit u : group) {
u.move(100, 200);
}
//Battlecruiser 객체를 가진 3번째 배열 요소가 move() 메서드를 호출할 때 제대로된 동작을 하지 않게됨
}
}
추상 메서드를 통한 강제 구현으로 프로그램 스펙 수정/추가시 일어 날 수 있는 문제점을 미리 방지, 안정적이고 구조적인 프로그래밍 가능
규격에 맞는 설계 구현
- 실제 프로젝트에서 어플리케이션 아키텍쳐가 설계해 놓은 추상 클래스를 상속 받고, 개발자는 프로젝트에서 필요하고 공통적으로 들어가야하는 필드와 메서드를 오버라이딩해서 큰 설계를 생각할 필요 없이 구현만 하면된다.
- 초기 설계 시간이 절약되고, 구현에만 집중이 가능하다.