본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석4-1 추상 클래스(abstract class)와 추상메서드

by 이쟝 2022. 1. 5.

추상 클래스(abstract class)

- 미완성 설게도. 미완성 메서드(추상메서드)를 갖고 있는 클래스

- 추상 클래스 자체로는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는 데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 갖는다. 

- 추상 클래스 -> iv(인스턴스 변수), 생성자, im(인스턴스 메서드), 추상 메서드

 

추상클래스의 구조

abstract class 클래스이름 {

                 ....

}

 

abstract class Player {                 // 추상클래스(미완성클래스)
       abstract void play(int pos);     // 추상메서드
       abstract void stop();           // 추상메서드
}

 

-> 다른 클래스 작성에 도움을 주기 위한 것이기 때문에 인스턴스 생성 불가하다!

Player p = new Player();  // 에러. 추상클래스의 인스턴스 생성 불가

 

-> 상속을 통해 추상 메서드를 완성해야 인스턴스 생성가능

 

class AudioPlayer extends player {
	void player(int pos) { /*내용*/ }   // 추상메서드 구현(몸통생성)
	void stop() { /*내용*/ }		      // 추상메서드 구현(몸통생성)
}

AudioPlayer ap = new AudioPlayer();  // ok!
//Player ap = new AudioPlayer( ); 가능 왜? 다형성!!

 

더보기

AudioPlayer는 추상메서드가 아닌 완성된 설계도 이기 때문에 abstract이 없어도 됨!


추상 메서드(abstract method)

- 미완성 메서드(추상메서드) -> 구현부(몸통{ })가 없는 메서드(선언부만 작성)

- 상속받아서 자손클래스가 추상 메서드를 완성했지만 상속받은 메서드를 다 구현하는 게 아니라 일부만 구현한다면 abstract을 꼭 써줘야 함(그렇지 않으면 여전히 추상메서드이기 때문에 에러!) 

- 추상클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 추상메서드를 구현한다!

 

- 왜 메서드를 미완성 상태로 남겨놓은 걸까? 

->  메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에!

-> 선언부만 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워 두는 것

 

추상메서드의 구조

/* 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다. */

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) { }                   // 추상메서드를 구현
}
-> 마지막 코드는 추상메서드를 구현하긴 했지만 play메서드만 구현했고, 
stop메서드는 구현하지 않아서 여전히 추상클래스! 그래서 앞에 abstract이 붙는다.

 


추상 클래스의 작성과 장점

- 여러 클래스에 공통적으로 사용될 수 있는 추상클래스를 바로 작성하거나 기존클래스의 공통 부분을 뽑아서 추상클래스를 만든다.

 

상속: 자손클래스를 만드는데 조상클래스를 사용 <-> 추상화: 기존의 클래스의 공통부분을 뽑아내서 조상 클래스를 만드는 것

구체화: 상속을 통해 클래스를 구현, 확장 <-> 추상화: 클래스 간의 공통점을 찾아내서 공통의 조상을 만드는 작업

 

추상클래스의 장점

1) 설계도를 쉽게 작성할 수 있다. 

2) 중복 제거 가능 

3) 관리가 용이(변경)

4) 추상화된 코드는 구체화된 코드보다 유연해서 변경에 유리!

 

예제1) Player2라는 추상클래스를 작성. 이 클래스는 VCR, Audio의 조상으로 사용될 수 있음

 

abstract class Player2 {
	boolean pause;        // 일시정지 상태를 저장하기 위한 변수
	int currentPos;       // 현재 Play되고 있는 위치를 저장하기 위한 변수
	
	Player2() {           // 추상클래스도 생성자가 있어야 한다.
		pause = false;
		currentPos = 0; 
	}
	
	// 지정된 위치(pos)에서 재생을 시작하는 기능이 수행하도록 작성 
	abstract void play(int pos);    // 추상메서드
	// 재생을 즉시 멈추는 기능이 수행하도록 작성
	abstract void stop();           // 추상메서드
	
	void play() {
		play(currentPos);           // 추상메서드를 사용할 수 있다. 
	}
	
	void pause() {
		if(pause) {            // pause가 true일 때 (정지상태)에서 pause가 호출되면,
			pause = false;     // pause의 상태를 false로 바꾸고, 
			play(currentPos);  // 현재의 위치에서 play를 한다. 
		} else {               // pause가 false일 때 (play상태)에서 pause가 호출되면,
			pause = true;      // pause의 상태를 true로 바꾸고,
			stop();	           // play를 멈춘다. 
		}
	}
}

 

예제2) Player2클래스를 조상으로 하는 CDPlayer 클래스 생성

 

class CDPlayer extends Player2 {
	void play(int currentPos) {
		// 조상의 추상메서드를 구현
	}
	void stop() {
		// 조상의 추상메서드를 구현
	}
	//CDPlayer클레스에 추가로 정의된 멤버
	int currentTrack;  // 현재 재생중인 트랙
	
	void nextTrack() {
		currentTrack++;
	}
	void preTrack() {
		if(currentTrack>1) {
			currentTrack--;
		}
	}
}

 

더보기

- 조상 클래스의 추상메서드를 CDPlayer클래스의 기능에 맞게 완성해주고, CDPlayer만의 새로운 기능을 추가.

- 아무런 내용 없이 단지 괄호{ }만 있어도, 추상메서드가 아닌 일반 메서드로 간주한다. 

-> abstract을 사용해 추상메서드로 정의해놓으면, 자손 클래스를 작성할 때 내용을 구현해주어야 한다는 사실을 인식하고 자신의 클래스에 알맞게 구현할 것!


예제3) 기존의 클래스로부터 공통된 부분을 뽑아내어 추상클래스 생성

 

 

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) { System.out.println("Marine[x="+x+",y="+y+"]"); } // 추상메서드 구현
	void stimPack()         { /* 스팀팩을 사용 */ }
}

class Tank extends Unit {  // 탱크
	void move(int x, int y) { System.out.println("Tank[x="+x+",y="+y+"]"); }
	void changeMode() { /* 공격모드로 변환 */ }
}

class Dropship extends Unit { // 수송선
	void move(int x, int y) { System.out.println("Dropship[x="+x+",y="+y+"]"); }
	void load() { /* 선택된 대상을 태운다 */ }
	void unload() { /* 선택된 대상을 내린다. */ }
}

 

- 각 클래스의 공통부분(move)을 뽑아내서 Unit 클래스를 정의하고 이로부터 상속받도록 함

 

-> move 메서드가 추상메서드로 선언된 것은, 앞으로 Unit클래스를 상속받아서 작성되는 클래스는 move메서드를 자신의 클래스에 알맞게 반드시 구현해야 한다는 의미!

 

Unit[] group = { new Marine(), new Tank(), new Dropship() }; // group 객체 배열 생성
		
for(int i=0; i<group.length; i++) 
	group[i].move(100, 200);   // Unit 배열의 모든 유닛을 좌표(100,200)의 위치로 이동시킨다.

 

-> 조상클래스타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있기 떄문에 조상클래스타입의 배열에 자손클래스의 인스턴스를 담을 수 있음!!!!!!(다형성)

 

-> Unit클래스에 move메서드가 추상메서드로 정의되어 있지만 Unit클래스 타입의 참조변수로 move메서드를 호출하는 것이 가능!!