클래스를 설계도에 비유한다면, 추상클래스는 미완성 걸계도에 비유할 수 있다. 미완성 설계도로 완성된 제품을 만들 수 없듯이 추상클래스로 인스턴스는 생성할 수 없다. 추상클래스는 상속을 통해서 자손클래스에 의해서만 완성될 수 있다.
추상클래스 자체로는 클래스로서의 역할을 다 못하지만, 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 갖는다. 새로운 클래스를 작성할 때 아무 것도 없는 상태에서 시작하는 것보다는 완전하지는 못하더라도 어느 정도 틀을 갖춘 상태에서 시작하는 것이 나을 것이다.
abstract class 클래스이름 { ... }
추상 클래스는 추상 메서드를 포함하고 있다는 것을 제외하고는 일반 클래스와 전혀 다르지 않다. 추상 클래스에도 생성자가 있으며, 멤버변수와 메서드도 가질 수 있다.
메서드는 선언부와 구현부(몸통)로 구성되어 있다. 선언부만 작성하고 구현부는 작성하지 않은채로 남겨눈 것이 추상메서드이다.
메서드를 이와 같이 미완성 상태로 남겨 놓는 이유는 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서는 선언부만을 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려 주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워 두는 것이다.
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) {} //추상메서드를 구현 }
굳이 abstract를 붙여서 추상메서드로 선언하는 이유는 자손 클래스에서 추상메서드를 반드시 구현하도록 강요하기 위해서이다.
다형성에서 배웠듯이 조상 클래스타입의 참조변수로 자손 클래스의 인스턴스를 참조하는 것이 가능하기 때문에, 조상 클래스 타입의 배열에 자손 클래스의 인스턴스를 담을 수 있다.
Run()은 Car 클래스에 이미 구현된 메서드입니다. 이렇게 템플릿 메서드의 역할은 메서드 실행 순서와 메서드 실행 순서와 시나리오를 정의하는 것입니다. 템플릿 메서드에서 호출하는 메서드가 추상 메서드라면 차종에 따라 구현 내용이 바뀔 수는 있습니다. AICar 와 MannualCar 작동 방식의 일부가 다른 것처럼 말이죠. 하지만 시동을 켜고, 달리고, 멈추고, 시동을 끄는 시나리오는 변하지 않습니다. 이런 메서드를 템플릿 메서드로 정의하는 것입니다. 템플릿 메서드는 실행 순서, 즉 시나리오를 정의한 메서드이므로 바뀔 수는 없습니다. 상위 클래스를 상속받은 하위 클래스에서 템플릿 메서드를 재정의하면 안 된다는 것입니다. 그래서 템플릿 메서드는 final 예약어를 사용해 선언합니다. 메서드 앞에 final을 사용하면 상속받은 하위 클래스가 메서드를 재정의할 수 없습니다