1. 추상 클래스
1-1 추상 클래스란?
- 추상적이란 것은 구체적이지 않고 막연한 것을 뜻합니다.
- '어떤 클래스가 추상적이다'라는 말은 '구체적이지 않은 클래스'라는 뜻입니다.
- 추상 클래스를 영어로 표현하면 abstract class, 추상 클래스가 아닌 클래스를 concrete class라고 합니다.
- 우리가 지금까지 만든 클래스는 모두 concrete class였습니다.
- 추상 클래스는 항상 추상 메서드를 포함하며 구현 코드가 없습니다.
- 구현 코드가 없다는 것은 함수 몸체(body)가 없다는 뜻입니다.

- 중괄호 { }로 감싼 부분을 함수의 구현부(implementation)라고 합니다.
- 이 부분이 없는 함수는 추상 함수(abstract function)이고 자바에서는 추상 메서드(abstract method)라고 합니다.
- 추상 메서드는 다음과 같이 선언만 하며 abstract 예약어를 사용하며, { } 대신 ;를 사용합니다.

- 정리하면, 자바에서 추상 메서드는 abstract 예약어를 사용하여 선언한 메서드입니다.
메서드 선언의 의미
- 로직을 구현하는 것보다 더 중요한 것이 어떻게 구현할지를 결정하는 것입니다.
- 이런 과정을 개발 설계라고 합니다.
- 물론 설계 과정은 더 복잡하고 다양한 방법이 있을 수 있습니다.

- 위 코드처럼 선언한 메서드를 보면 두 개의 정수를 입력 받은 후 더해서 그 결과 값을 반환한다는 것을 유추할 수 있습니다.
- 즉 이 메서드의 선언부(declatration)만 봐도 어떤 일을 하는 메서드인지 알 수 있습니다.
- 함수의 선언부 즉 반환 값, 함수 이름, 매개 변수를 정의한다는 것은 곧 함수의 역할이 무엇인지, 어떻게 구현해야 하는지를 정의한다는 뜻입니다.
- 따라서 함수 몸체를 구현하는 것보다 중요한 것은 함수 선언부를 작성하는 것입니다.
- 이와 마찬가지로 자바에서 메서드를 선언한다는 것은 메서드가 해야 할 일을 명시해 두는 것입니다.
1-2 추상 클래스 구현하기
예제 9-1 추상 클래스 구현하기
public class Computer {
// 오류 발생
public void display();
public void typing();
public void turnOn() {
System.out.println("전원을 켭니다.");
}
public void turnOff() {
System.out.println("전원을 끕니다.");
}
}
- 클래스 내부에 추상 메서드 display( )와 typing( )을 선언하고, 구현 메서드 turnOn( )과 turnOff( )를 작성합니다
- 완전하게 구현하지 않은 두 추상 메서드에서 오류가 발생합니다.
- display( )나 typing( ) 위에 마우스를 올리면 오류를 해결할 수 있는 방법이 뜨고, 두번째 옵션을 선택하면 display( )와 typing( )에 abstract 예약어가 생깁니다.
// 오류 발생
public class Computer {
// abstract 추가했지만 오류가 남아있음
public abstract void display();
public abstract void typing();
......
}
- 하지만 이번에는 메서드와 클래스 이름에 모두 오류가 표시됩니다.
- 추상 메서드가 속한 클래스를 추상 클래스로 선언하지 않았기 때문입니다.
- 클래스도 추상 클래스로 만들면 해결이 됩니다.
// abstract 추가
public abstract class Computer {
// abstract 추가
public abstract void display();
public abstract void typing();
public void turnOn() {
System.out.println("전원을 켭니다.");
}
public void turnOff() {
System.out.println("전원을 끕니다.");
}
}
- Computer 클래스를 이와 같이 구현한 것은 'Computer를 상속받는 클래스 중 turnOn( )과 turnOff( ) 구현 코드는 공통이다.
- 하지만 display( )와 typing( )은 하위 클래스에 따라 구현이 달라질 수 있습니다.
- 그래서 Computer에서는 구현하지 않고, '이 두 메서드 구현에 대한 책임을 상속받는 클래스에 위임한다'라는 의미입니다.
- 따라서 Computer 클래스의 추상 메서드는 추상 클래스를 상속받은 DeskTop과 NoteBook에서 실제로 구현하게 됩니다.
- 상위 클래스에서는 하위 클래스도 공통으로 사용할 메서드를 구현하고, 하위 클래스마다 다르게 구현할 메서드는 추상 메서드로 선언해 두는 것입니다.
예제 9-2 추상 메서드 구현하기
public class DeskTop extends Computer {
@Override
public void display() {
// 추상 메서드 몸체 작성
System.out.println("DeskTop display()");
}
@Override
public void typing() {
// 추상 메서드 몸체 작성
System.out.println("DeskTop typing()");
}
}
- 추상 클래스를 상속받은 하위 클래스는 구현되지 않은 추상 메서드를 모두 구현해야 구체적인 클래스가 됩니다.
- DeskTop은 추상 메서드를 모두 구현했으므로 추상 클래스는 아닙니다.
예제 9-3 NoteBook 클래스 구현하기
public abstract class NoteBook extends Computer {
@Override
public void display() {
System.out.println("NoteBook display()");
}
}
- 상속받은 추상 메서드를 모두 구현하지 않았습니다.
- 추상 메서드를 모두 구현하지 않아, 추상 메서드가 남았기 때문에 NoteBook은 추상 클래스가 됩니다.
예제 9-4 MyNoteBook 클래스 구현하기
public class MyNoteBook extends NoteBook {
@Override
public void typing() {
System.out.println("MyNoteBook typing()");
}
}
- MyNoteBook은 typing( ) 메서드만 구현했습니다.
- NoteBook을 상속받았으므로 display( )까지 구현이 된 것으로 모든 추상 메서드가 구현된 클래스이므로 abstract 예약어를 사용하지 않아도 됩니다.
1-3 추상 클래스를 만드는 이유
예제 9-5 추상 클래스 테스트하기
public class ComputerTest {
public static void main(String[] args) {
// Computer에서 오류가 발생 - 클래스를 인스턴스로 생성 불가
Computer c1 = new Computer();
Computer c2 = new DeskTop();
// NoteBook에서 오류가 발생 - 클래스를 인스턴스로 생성 불가
Computer c3 = new NoteBook();
Computer c4 = new MyNoteBook();
}
}
- Computer 클래스형으로 인스턴스를 4개 생성하였으나, Computer와 NoteBook에서 클래스를 인스턴스로 생성할 수 없다는 오류가 발생합니다.
- 추상 클래스는 모든 메서드가 구현되지 않았으므로 인스턴스로 생성할 수 없습니다.
추상 클래스에서 구현하는 메서드
- 추상 클래스는 상속을 하기 위해 만든 클래스입니다.
- 추상 클래스에서 구현하는 메서드는 하위 클래스에서도 사용할, 즉 하위 클래스에서도 구현 내용을 공유할 메서드를 구현합니다.
- 실제 하위 클래스에서 내용을 다르게 구현해야 한다면, 구현 내용을 추상 메서드로 남겨 두고 하위 클래스에 구현을 위임하는 것입니다.
2. 템플릿 메서드
2-1 추상 클래스와 템플릿 메서드
- '템플릿(template)'이란 틀이나 견본을 뜻하므로, 템플릿 메서드란 틀이 있는 메서드라는 의미입니다.
- 앞선 클래스와 객체 파트에서 소개한 싱글톤 패턴과 같은 디자인 패턴입니다.
- 템플릿 메서드는 추상 클래스를 사용하여 구현할 수 있습니다.
예제 9-6 추상 클래스와 템플릿 메서드(1)
public abstract class Car {
public abstract void drive();
public abstract void stop();
public void startCar() {
System.out.println("시동을 켭니다.");
}
public void turnOff() {
System.out.println("시동을 끕니다.");
}
// 템플릿 메서드
final public void run() {
startCar();
drive();
stop();
turnOff();
}
}
- Car 클래스는 drive( )와 stop( ) 추상 메서드와 3개의 구현된 메서드 startCar( ), turnOff( ), run( )을 가지고 있습니다.
- 자동차의 시동을 켜거나 끄는 방법은 어느 차에서나 동일하므로, startCar( )와 trunOff( )는 미리 코드를 구현했습니다.
- drive( )와 stop( )은 차종에 따라 다른 방식으로 움직일 수 있으므로 추상 메서드로 선언합니다.
예제 9-7 추상 클래스와 템플릿 메서드(2)
public class AICar extends Car {
@Override
public void drive() {
System.out.println("자율 주행합니다.");
System.out.println("자동차가 알아서 방향을 전환합니다.");
}
@Override
public void stop() {
System.out.println("스스로 멈춥니다.");
}
}
- AICar 클래스는 Car 클래스를 상속 받았고 drive( )와 stop( ) 추상 메서드를 구현했습니다.
- AICar는 자율주행을 하고 방향도 알아서 바꿈, 사람은 시동을 켜고 끄는 일만 하면 됩니다.
예제 9-8 추상 클래스와 템플릿 메서드(3)
public class ManualCar extends Car {
@Override
public void drive() {
System.out.println("사람이 운전합니다.");
System.out.println("사람이 핸들을 조작합니다.");
}
@Override
public void stop() {
System.out.println("브레이크로 정지합니다.");
}
}
- ManualCar 클래스는 Car 클래스를 상속 받았고 drive( )와 stop( ) 추상 메서드를 구현했습니다.
- 사람이 직접 핸들을 조작하면서 달리고 있으며, 정지를 하려면 브레이크를 밟으면 됩니다.
예제 9-9 추상 클래스와 템플릿 메서드(4)
public class CarTest {
public static void main(String[] args) {
System.out.println("===== 자율 주행하는 자동차 =====");
Car AICar = new AICar();
AICar.run();
System.out.println("===== 사람이 운전하는 자동차 =====");
Car ManualCar = new ManualCar();
ManualCar.run();
}
}

2-2 템플릿 메서드의 역할
- CarTest에서 두 개의 인스턴스(AICar, ManualCar)를 생성하고 run( )을 호출했습니다.
- run( )은 Car 클래스에 이미 구현된 메서드입니다.
- 템플릿 메서드의 역할은 메서드 실행 순서와 시나리오를 정의하는 것입니다.
- 템플릿 메서드에서 호출하는 메서드가 추상 메서드라면 차종에 따라 구현 내용이 바뀔 수 있습니다.
- 공통적인 시동을 켜고, 달리고, 멈추고, 시동을 끄는 시나리오는 변하지 않습니다.
- 템플릿 메서드는 실행 순서, 즉 시나리오를 정의한 메서드이므로 바뀔 수 없습니다.
- 상위 클래스를 상속 받은 하위 클래스에서 템플릿 메서드를 재정의하면 안 된다는 의미입니다.
- 그러므로 final 예약어를 사용해 선언합니다.
- 템플릿 메서드는 로직 흐름이 이미 정해져 있는 프레임워크에서 많이 사용하는 기본 구현 방법입니다.
3. 템플릿 메서드 응용하기
3-1 클래스 기능과 관계
- 무조건 클래스를 만들어 코딩하는 것보다 주어진 문제를 어떻게 해결할 것인지를 천천히 생각해 보고 손으로 클래스 다이어그램을 간략하게 그려 보는 것이 객체 지향 방식으로 문제를 해결하는 좋은 습관입니다.
- 큰 프로젝트를 진행할 때는 이 과정을 분석 · 설계 과정이라고 함
3-2 클래스 설계하기
- Player 클래스와 PlayerLevel 클래스는 포함(HAS-A) 관계입니다.
Player 클래스
- Player는 한 번에 하나의 레벨 상태이므로 level 변수에 레벨에 해당하는 인스턴스를 대입함
- 레벨을 변경할 수 있는 upgradeLevel( ) 메서드도 만듭니다.
예제 9-10 Player 클래스 구현하기
public class Player {
// Player가 가지는 level 변수 선언
private PlayerLevel level;
// 디폴트 생성자. 처음 생성되면 BeginnerLevel로 시작하며 레벨 메세지 출력
public Player() {
level = new BeginnerLevel();
level.showLevelMessage();
}
public PlayerLevel getLevel() {
return level;
}
// 레벨 변경 메서드, 현재 자신의 level을 매개변수로 받은 level로 변경하고 레벨 메세지 출력
// 매개변수 자료형은 모든 레벨로 변환 가능한 PlayerLevel
public void upgradeLevel(PlayerLevel level) {
this.level = level;
level.showLevelMessage();
}
// PlayerLevel의 템플릿 메서드 go() 호출
public void play(int count) {
level.go(count);
}
}
PlayerLevel 클래스
- 각 레벨에서 수행할 공통 기능은 PlaberLevel 추상 클래스에서 선언함
예제 9-11 PlayerLevel 추상 클래스 구현하기
public abstract class PlayerLevel {
public abstract void run();
public abstract void jump();
public abstract void turn();
public abstract void showLevelMessage();
// 재정의 되면 안되므로 final로 선언
final public void go(int count) {
run();
for(int i = 0; i < count; i++) {
jump();
}
turn();
}
}
- 각 레벨마다 run( ), jump( ), turn( ), showLevelMessage( ) 메서드는 조금씩 다르게 구현되기 때문에 추상 메서드로 선언합니다.
- go( ) 메서드는 시나리오대로 수행되어야 하므로 코드 내용을 완전히 구현합니다.
- Player가 go( )를 호출하면 run( ), jump( ), turn( ) 메서드가 순서대로 호출될 것입니다.
- 모든 레벨에서 동일하고 변하면 안 되는 것이므로 final 예약어를 사용해 템플릿 메서드로 구현합니다.
- 각 레벨에서는 해당 레벨별로 제공하는 run( ), jump( ), turn( ), showLevelMessage( ) 기능을 구현합니다.
초보자 레벨 클래스
- 초보자 레벨에서는 천천히 달릴 수만 있습니다.
- 점프나 턴은 할 수 없습니다.
예제 9-12 초보자 레벨 클래스 구현하기
public class BeginnerLevel extends PlayerLevel {
@Override
public void run() {
System.out.println("천천히 달립니다.");
}
@Override
public void jump() {
System.out.println("Jump할 줄 모르지롱");
}
@Override
public void turn() {
System.out.println("Turn할 줄 모르지롱");
}
@Override
public void showLevelMessage() {
System.out.println("===== 초보자 레벨입니다 =====");
}
}
중급자 레벨 클래스
- 중급자 레벨에서는 빠르게 달릴 수 있고 높이 점프할 수 있습니다.
- 아직 턴하는 기술은 사용하지 못합니다.
예제 9-13 중급자 레벨 클래스 구현하기
public class AdvancedLevel extends PlayerLevel {
@Override
public void run() {
System.out.println("빨리 달립니다.");
}
@Override
public void jump() {
System.out.println("높이 Jump합니다.");
}
@Override
public void turn() {
System.out.println("Turn할 줄 모르지롱");
}
@Override
public void showLevelMessage() {
System.out.println("===== 중급자 레벨입니다 =====");
}
}
고급자 레벨 클래스
- 고급자 레벨에서는 엄청 빠르게 달릴 수 있고 아주 높게 점프할 수 있습니다.
- 한바퀴 턴하는 기술까지 사용할 수 있습니다.
예제 9-14 고급자 레벨 클래스 구현하기
package study.gameLevel;
public class SuperLevel extends PlayerLevel {
@Override
public void run() {
System.out.println("엄청 빨리 달립니다.");
}
@Override
public void jump() {
System.out.println("아주 높이 Jump합니다.");
}
@Override
public void turn() {
System.out.println("한 바퀴 돕니다.");
}
@Override
public void showLevelMessage() {
System.out.println("===== 고급자 레벨입니다 =====");
}
}
테스트 프로그램 작성해서 실행하기
예제 9-15 테스트 프로그램 실행하기
public class MainBoard {
public static void main(String[] args) {
// 객체 생성. 처음 생성하면 BeginnerLevel
Player player = new Player();
player.play(1);
AdvancedLevel aLevel = new AdvancedLevel();
player.upgradeLevel(aLevel);
// 2이므로 점프 2번
player.play(2);
SuperLevel sLevel = new SuperLevel();
player.upgradeLevel(sLevel);
// 3이므로 점프 3번
player.play(3);
}
}

- Player 클래스의 디폴트 생성자는 초보자 레벨로 시작하도록 구현되어 있습니다.
- Player 클래스를 생성하고 인스턴스를 player 참조 변수에 대입합니다.
- play( ) 메서드의 매개변수에 1을 입력하면 초보자 레벨이기 때문에 천천히 달린 후 Jump할 줄 모른다는 문구와 Turn할 줄 모른다는 문구를 출력합니다.
3-3 추상 클래스와 다형성
- 상위 클래스인 추상 클래스는 하위에 구현된 여러 클래스를 하나의 자료형(상위 클래스 자료형)으로 선언하거나 대입할 수 있습니다.
- 추상 클래스에 선언된 메서드를 호출하면 가상 메서드에 의해 각 클래스에 구현된 기능이 호출됩니다.
- 하나의 코드가 다양한 자료형을 대상으로 동작하는 다형성을 활용할 수 있는 것입니다.
- 모든 추상 클래스에 템플릿 메서드를 사용하는 것은 아니지만, 추상 클래스를 활용할 수 있는 좋은 패턴입니다.
4. final 예약어
- final 예약어는 마지막으로 정한 것이니 더 이상 수정할 수 없다는 뜻입니다.
- 자바 프로그램에서 final 예약어는 변수, 메서드, 클래스에 사용할 수 있습니다.

4-1 상수를 의미하는 final 변수
예제 9-16 final 실습하기
public class Constant {
int num = 10;
// 상수 선언
final int NUM = 100;
public static void main(String[] args) {
Constant cons = new Constant();
cons.num = 50;
// 상수에 값을 대입하여 오류 발생
// cons.NUM = 200;
System.out.println(cons.num);
System.out.println(cons.NUM);
}
}
- 두 개의 변수 int num과 final int NUM을 선언합니다.
- 변수 이름은 대소문자를 구별하기 때문에 두 변수는 다른 변수입니다.
- 상수를 선언할 때는 일반 변수와 구별하기 위해 대문자로 쓰는 경우가 많습니다.
- 상수로 선언한 NUM에는 다른 값을 대입할 수 없기 때문에 새로운 값을 대입하면 오류가 발생합니다.
여러 자바 파일에서 공유하는 상수 값 정의하기
- 하나의 자바 파일에서만 사용하는 상수 값은 해당 파일 안에서 정의해서 사용할 수 있습니다.
- 자바로 프로젝트를 진행할 때 여러 파일에서 공유해야 하는 상수 값은 한 파일에 모아서 public static final로 선언하여 사용하면 좋습니다.
예제 9-17 여러 파일에서 공유하는 상수
public class Define {
public static final int MIN = 1;
public static final int MAX = 99999;
public static final int ENG = 1001;
public static final int MATH = 2001;
public static final double PI = 3.14;
public static final String GOOD_MORNING = "Good Morning";
}
- 상수를 모두 public 예약어로 선언했으므로 이 값들은 외부에서도 사용할 수 있습니다.
- 모든 상수를 static으로 선언했기 때문에 인스턴스를 생성하는 것과 관계없이 클래스 이름으로 참조할 수 있습니다.
예제 9-18 상수 사용하기
public class UsingDefine {
public static void main(String[] args) {
// static으로 선언했으므로 인스턴스를 생성하지 않고 클래스 이름으로 참조 가능
System.out.println(Define.GOOD_MORNING);
System.out.println("최솟값은 " + Define.MIN + "입니다.");
System.out.println("최댓값은 " + Define.MAX + "입니다.");
System.out.println("영어 과목 코드 값은 " + Define.ENG + "입니다.");
System.out.println("수학 과목 코드 값은 " + Define.MATH + "입니다.");
}
}

4-2 상속할 수 없는 final 클래스
- 클래스는 final로 선언하면 상속할 수 없습니다.
- 상속을 하면 변수나 메서드를 재정의할 수 있는데, 그러면 원래 클래스가 가지고 있는 기능에 오류가 생길 수도 있습니다.
- 보안과 관련되어 있거나 기반 클래스가 변하면 안 되는 경우에는 클래스를 final로 선언합니다.
4-3 프로그램을 잘 구현하는 또 다른 방법
- 책 내용을 따라 코딩을 해보았다면, 거꾸로 진행해 보는 방법이 있습니다.
- 테스트 코드를 먼저 개발하는 방법론을 테스트 주도 개발(Test Driven Development: TDD)라고 합니다.