본문 바로가기
Programming/Java

[Java] 15. 추상 클래스

by Rayched 2023. 1. 26.

1. 개요

Abstract, '추상적'이라는 의미를 가지고 있는 단어이다.

추상적이라는 것은 어떠한 사물이 직접 경험하거나 지각할 수 있는 일정한 형태나 성질을 갖추지 않은 것이다.

즉, 구체적인 것이 존재하지 않고 막연하기만 한 것을 추상적이라고 한다.

프로그래밍, 자바에서도 이와 비슷하게 구체적인 형태가 존재하지 않은 클래스 문법을

'추상 클래스(Abstract Class)'라고 한다.


2. 추상 클래스 (Abstract Class)

① 추상 클래스의 정의

- 미완성된 설계도(클래스, class)

- 미완성된 메서드, 추상 메서드를 가진 클래스

- 클래스 앞에 추상적이라는 의미를 가진 'abstract' 단어를 붙여서 정의한다.

- new() 연산자를 통해서 객체를 만들 수 없지만, 상속을 통해서 추상 메서드를 완성하는 것으로

  추상 클래스가 가진 메서드를 사용할 수 있다.

//추상 클래스 정의

abstract class Noname {
	//추상 클래스 Noname
	
	//추상 메서드
	//미완성된 메서드
	//구현부('{}')가 없고, 리턴 타입 앞에 abstract를 붙여서 정의한다.
	abstract int Number(); //추상 메서드 예시
	
	void Hello(){
		System.out.println("Hello.");
	}
	//추상 클래스 내에는 추상 메서드가 아닌 일반 메서드도 존재할 수 있다.
	
	abstract String Text(){} //error 발생함
	//abstract 키워드는 구현부가 존재하는 메서드에는 붙일 수 없다.
	//따라서 구현부가 존재하는 이 메서드는 abstract 키워드를 붙여서는 안된다.
}

② 추상 클래스 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//추상 클래스 예제
//추상 클래스인 무기를 만들고, 추상 메서드 text() 정의
//이를 칼, 활 클래스가 상속받고 추상 메서드 text()를 
 
public class Main {
    public static void main(String[] args){
        칼 a칼 = new 칼();
        a칼.text();
 
        활 a활 = new 활();
        a활.text();
    }
}
 
abstract class 무기 {
    abstract void text();
}
 
class 칼 extends 무기 {
    void text(){
        System.out.println("칼 작동");
    }
}
 
class 활 extends 무기 {
    @Override
    void text() {
        System.out.println("활 작동");
    }
}
cs

 

프로그램 실행, 이상 없이 잘 작동된다.

추상 클래스 '무기'와 '무기'의 추상 메서드 'text()'를 만들었다.

그리고 칼, 활 클래스로 상속을 받아서 추상 메서드 'text()'를 완성시켰다.

추상 메서드를 완성하는 것까지 끝냈으니 한 가지만 더 시험하고 설명을 마치겠다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//추상 클래스와 인터페이스
 
class Main {
  public static void main(String[] args) {
    Sword aSword = new Sword();
    aSword.Start(); //칼 작동
 
    Gun aGun = new Gun();
    aGun.reload(); //error
    
  }
}
 
abstract class Weapon {
  abstract void Start();
}
 
class Sword extends Weapon {
  void Start(){
    System.out.println("칼 작동");
  }
}
 
 
class Gun extends Weapon {
  //부모 클래스인 Weapon한테 상속받은
  //추상 메서드 Start()를 완성하지 않음
  void reload(){
    System.out.println("장전");
  }
}
cs

해당 소스코드는 추상 클래스인 Weapon으로부터 상속받은 Gun 클래스에서

추상 메서드 'Start()'를 완성하지 않고, 인스턴스화를 할 수 있는지를 시험해 본 것이다.

프로그램은 실행되지 않고, 컴파일 에러가 발생한다.

에러 메시지를 확인해 보자.

Weapon: 부모 클래스, 추상 클래스

Gun: 자식 클래스

 

Weapon한테 상속받은 Start() 메서드를 완성하지 않은

Gun 클래스는 추상 클래스가 돼버린 상태이다.

앞에서 말했던 것처럼 new 연산자로 추상 클래스의 객체를 만들 수 없기 때문에

컴파일 에러가 발생하게 된다.

 

Gun 클래스에서 추상 메서드를 완성하고 프로그램을 다시 실행해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//추상 클래스와 인터페이스
 
class Main {
  public static void main(String[] args) {
    Sword aSword = new Sword();
    aSword.Start(); //칼 작동
 
    Gun aGun = new Gun();
    aGun.Start();  //총 작동
    aGun.reload(); //장전
    
  }
}
 
abstract class Weapon {
  abstract void Start();
}
 
class Sword extends Weapon {
  void Start(){
    System.out.println("칼 작동");
  }
}
 
 
class Gun extends Weapon {
  //Before
  //부모 클래스인 Weapon한테 상속받은
  //추상 메서드 Start()를 완성하지 않음
  
  void Start(){
    System.out.println("총 작동");
  }
  //After
  //추상 메서드 Start를 완성함(Override)
  void reload(){
    System.out.println("장전");
  }
}
cs


③ 추상 클래스를 사용하는 이유

추상 클래스는 상속을 강제하기 위해서 사용한다.

부모 클래스에서 메서드의 기본적인 형태만 정의하고

자식 클래스에서 이를 상속받아서 자기 자신한테 필요한 형태로 재정의를 하게 한다.

아래의 예제를 통해서 추상 클래스를 사용하는 이유에 대해 나름대로 정리해 봤다.

//추상 클래스의 사용 이유
//rpg 게임을 만든다고 가정한다.
//만들 직업은 전사, 마법사, 초보자 세 가지이다.
//전사는 사용하는 무기 종류에 따라 사용하는 스킬이 달라진다.
//단, 무기를 정하지 않았다면 기본 스킬만 사용한다. (기본 무기: 한손검, 스킬: 베기)
//마법사는 사용하는 속성에 따라 쓰는 마법이 달라진다.
//속성을 정하지 않으면 기본 마법만 사용하고
//속성을 정하지 않았다는 문구가 나와야한다. (마법: 매직 미사일)
//캐릭터의 직업과 스킬을 정의하지 않으면 초보자로 나오게 해야한다.

abstract class Charactor {
    String Class_Name;
    String Class_Skill;

    Charactor(){
        this.Class_Name = "초보자";
        this.Class_Skill = "달팽이 세마리";
    } //캐릭터 직업을 정하지 않았다면
      //초보자로 나오게 설정함

    abstract void Active(); //캐릭터 행동
}

class Swordman extends Charactor {
    String Class_Name = "전사";
    String Class_Skill = "베기";

    String Weapon;

    Swordman(String Weapon){
        this.Weapon = Weapon;
    }

    void Active() {
        System.out.printf("%s가 공격합니다.\n", Class_Name);

        if(Weapon.equals("대검")){
            Class_Skill = "내려찍기";
            System.out.printf("사용 무기: %s\n", Weapon);
            System.out.printf("사용한 스킬: %s\n", Class_Skill);
        }
        else if(Weapon.equals("방패")){
            Class_Skill = "방패 밀기";
            System.out.printf("사용 무기: %s\n", Weapon);
            System.out.printf("사용한 스킬: %s\n", Class_Skill);
        }
        else {
            System.out.println("사용 무기: 한손검");
            System.out.printf("사용한 스킬: %s\n", Class_Skill);
            //무기를 정하지 않는다면 기본 스킬을 사용한다.
        }
    }
}

class Magician extends Charactor {
    String Class_Name = "마법사";
    String Magic_Name = "매직 미사일";
    String Elements;

    Magician(String Elements){
        this.Elements = Elements;
    }

    @Override
    void Active() {
        System.out.printf("%s가 공격합니다.\n", Class_Name);

        if(Elements.equals("불")){
            System.out.printf("캐릭터 속성 : %s\n", Elements);
            System.out.println("사용한 마법 : 파이어볼");
        }
        else if (Elements.equals("물")){
            System.out.printf("캐릭터 속성 : %s\n", Elements);
            System.out.println("사용한 마법 : 얼음화살");
        }
        else if (Elements.equals("전기")){
            System.out.printf("캐릭터 속성 : %s\n", Elements);
            System.out.println("사용한 마법 : 번개");
        }
        else {
            System.out.println("캐릭터의 속성이 없습니다.");
            System.out.println("사용한 마법 : 매직 미사일");
        }
    }
}

class Beginer extends Charactor {
    @Override
    void Active() {
        System.out.printf("%s가 공격합니다.\n", Class_Name);
        System.out.printf("사용한 스킬 : %s\n", Class_Skill);
    }
}

public class Main {
    public static void main(String[] args){
        Swordman aCharactor = new Swordman("대검");
        aCharactor.Active();

        System.out.println("=================="); //구분선

        Magician bCharactor = new Magician("전기");
        bCharactor.Active();

        System.out.println("=================="); //구분선

        Beginer abeginer = new Beginer();
        abeginer.Active();
    }
}

프로그램 실행 결과

부모 클래스인 Charactor에서 직업 이름(Class_Name), 직업 스킬(Class_Skill)

캐릭터의 행동을 정하는 추상 메서드 Active()를 정의하고

이를 Swordman(전사), Magician(마법사), Beginer(초보자)에서 상속받아서

추상 메서드 Active()를 각자 원하는 형태로 재정의를 했다는 것을 상단의 출력 결과를 통해서 알 수 있다.

 

만약 도적, 궁수, 해적 등의 직업을 더 추가한다고 치면

부모 클래스인 Charactor한테 기본적인 부분을 상속받고 이를 다시 원하는 형태로 재정의를 하고

만약 추상 메서드를 완성하지 않았다고 해도, 에러 메시지 등을 통해서 확인하는 것이 가능해진다.

 

그리고 만약 게임의 새로운 기능을 업데이트하려고 한다면

상위 클래스인 Charactor에서 추상 메서드로 기본적인 형태만 만들어 놓으면

이후에는 자식 클래스에서 해당 메서드를 가져와서 완성만 하면 되기 때문에

프로그램의 전체적인 유지보수가 편해진다는 점이 존재한다.


3. 마치며

길었다면 길다고 할 수 있었던 추상 클래스의 정리가 끝이 났다.

2023년 새해가 되면서 처음에 계획한 것은 1월 내에 자바 기초 문법 정리를 완료하는 것이었지만

필자가 게으른 탓인지 많이 늦어버린 감이 없잖아 있다.

아마 지금 작업 속도를 생각하면 늦어도 올해 3월까지는 정리를 끝낼 필요가 있을 것 같다.

 

'Programming > Java' 카테고리의 다른 글

[Java] 14. 다형성 (Polymorphism)  (0) 2023.01.20
[Java] 13. 오버로딩 (Overloading)  (0) 2023.01.17
[Java] 12. 캡슐화와 접근 제어자  (0) 2023.01.16
[Java] 11. 생성자 (Constructor)  (0) 2023.01.16
[Java] 10. 상속 (Inheritance)  (0) 2022.12.22