Java - Abstract

설명

일반적인 클래스는 구체적으로 데이터를 담아 인스턴스화 하여 직접 다루는 클래스입니다.

그 반대로 클래스는 구체적이지 않은 추상적인 데이터를 담고 있는 클래스입니다. 그래서 추상 클래스는 일반 클래스와 달리 인스턴스화가 불가능한 클래스이며, 추상 클래스를 선언할때는 abstract 키워드를 사용한다는 차이점이 있다고 말하곤 합니다.

실제로 어떤 앱을 만드냐에 따라 다르게 구현해야할 내용이 달라지므로, 따로 코드에서 구현하도록 하기 위해 선언만 해놓은 것이 추상 클래스의 추상 메서드입니다.

추상 클래스 기본 문법

자바에서는 abstract 키워드를 클래스명과 메서드명 옆에 붙임으로서 컴파일러에게 추상클래스와 추상 메서드임을 알려주게 됩니다. 추상 메서드는 작동 로직은 없고 이름이 있는 껍데기 메서드라고 보시면 됩니다. 즉, 메서드의 선언부만 작성하고 구현부는 미완성인 채로 남겨둔 메소드입니다.

보통 문법적인 측면으로 하나 이상의 추상 메소드를 포함하는 클래스를 가리켜 추상 클래스라고 정의합니다.

추상 클래스 안의 메서드를 미완성으로 남겨놓은 이유는 추상 클래스를 상속받는 자식클래스의 주제에 따라서 상속받는 메서드의 내용이 달라질 수 있기 때문입니다.

 

즉, 클래스의 선언부에 abstract 키워드가 있다는 말은 안에 추상 메서드(abstract method) 가 있으니 상속을 통해서 구현해주라는 지침이기도 합니다.

// 추상 클래스
abstract class Pet {
    abstract public void walk(); // 추상 메소드
    abstract public void eat(); // 추상 메소드
    
    public int health; // 인스턴스 필드
    public void run() {  // 인스턴스 메소드
    	System.out.println("run run");
    }
}

class Dog extends Pet {
	// 상속 받은 부모(추상) 메소드를 직접 구현
    public void walk() {
        System.out.println("Dog walk");
    }
    public void eat() {
    	System.out.println("Dog eat");
    }
}

public class main {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.eat();
        d.walk();
        d.run();
    }
}

 

추상 클래스는 클래스의 일종이라고 하지만 new 생성자를 통해 인스턴스 객체로 직접 만들 수 없습니다.
따라서 반드시 추상 클래스를 어느 자식의 클래스에 상속시키고, 자식클래스를 인스턴스화 하여 사용해야 합니다.

추상 클래스의 활용

추상 클래스는 미완성 설계도와 비슷합니다. 추상 클래스만으로는 인스턴스를 절대 생성할 수 없고 자식 클래스에서 상속받아야만 완성시킬 수 있기 때문입니다. 이처럼 추상 클래스는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는 데 있어서 바탕이 되는 부모클래스로서 아주 중요한 의미를 갖습니다.

 

공통 멤버의 통합으로 중복 제거

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() {} // 고유 능력 탑승 사용
}

 

이렇게 중복되는 코드를 아래와 같이 중복을 제거하고 코드 재사용성 증대 효과를 누릴 수 있게 됩니다.

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() {} // 고유 능력 탑승 사용
}

 

하지만 가만 생각해보면 굳이 추상 클래스로 선언할 필요가 없습니다.

abstract 키워드를 빼고 일반 부모 클래스로 선언해도 상속하고 중복을 제거하는데 전혀 문제가 없기 때문입니다.

class Unit { // abstract 뺌
    int x, y;
    void move(int x, int y) {} // abstract 뺌
    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() {} 
}

 

하지만 abstract 가 필요한 이유는 무엇일까요??

구현의 강제성

구현의 강제성을 통한 기능을 보장하기 때문입니다.

아래와 같은 코드가 있다고 생각해 봅시다.

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);
        }
    }
}

 

근데 유닛을 또 하나 추가하고 싶어 BattleCruiser 을 추가했다고 생각해 봅시다.

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 { ... }

 

그런데 개발자가 깜빡하고 배틀크루저의 이동에 관한 메소드(move())를 정의를 안했다고 가정해 봅시다.

치명적인 버그가 일어날 수도 있는 상황임에도 불구하고 만일 추상 클래스와 추상 메소드를 상속 관계를 맺었다면 이는 큰 문제가 되지 않습니다.

 

이렇게 강제적으로 구현하도록 도와주기 때문에 개발자가 실수를 해도 바로 고칠수 있게 됩니다.

만약 일반 클래스로 상속 관계를 맺는 상황에서 위와 같은 프로그래밍 실수가 일어나면 어떠한 에러도 내뿜지 않습니다.

왜냐하면 일반 클래스의 메서드를 오버라이딩 하든 안하든 자유이기 때문입니다.

이러한 특성 때문에 abstract 가 필요한 이유입니다.

 

정리

추상클래스를 상속받아서 미리 정의된 공통 기능들을 구현하고, 실제클래스에서 필요한 기능들을 클래스별로 확장시킴으로써 소스 수정시 다른 소스의 영향도를 적게 가져가면서 변화에는 유연하게 만들 수 있습니다.

또한 미리 규격에 맞게 소스가 구현되어 있기 때문에 해당 규격에 대한 구현부만 수정하면 손 쉽게 기능 수정이 가능합니다.

'Java' 카테고리의 다른 글

자바의 toString 메서드  (1) 2024.12.17
String  (0) 2024.12.17
업캐스팅 & 다운캐스팅  (1) 2024.12.11
자바 공부할 때 도움이 되는 용어 설명  (0) 2024.12.10
인터페이스  (0) 2024.12.10