추상 클래스의 개념
사전적 의미로 추상(abstract)은 실체 간에 공통되는 특성을 추출한 것을 말한다. 예를 들어, 새, 곤충, 물고기 등의 실체에서 공통되는 특성을 추출해보면 동물이라는 공통점이 있다. 이와 같이 동물은 구체적인 실체라기보다는 실체들의 공통되는 특성을 가지고 있는 추상적인 것이라고 볼 수 있다.
클래스에도 추상 클래스가 존재한다. 객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다면 이 클래스들의 공통적인 특성을 추출해서 선언한 클래스를 추상 클래스라고 한다. 추상 클래스와 실체 클래스는 상속의 관계를 가지고 있다. 추상 클래스가 부모이고, 실체 클래스가 자식으로 구현되어 실체 클래스는 추상 클래스의 모든 특성을 물려받고, 추가적인 특성을 가질 수 있다. 여기서 특성이란 필드와 메소드를 말한다. 예를 들어 Bird.class, Fish.class 등의 실체 클래스에서 공통되는 필드와 메소드를 따로 선언한 Animal.class 클래스를 만들 수 있는데, 이것이 바로 추상 클래스라고 할 수 있다.
추상 클래스는 실체 클래스의 공통되는 필드와 메소드를 추출해서 만들었기 때문에 객체를 직접 생성해서 사용할 수 없다. 다시 말해서 추상 클래스는 new 연산자를 사용해서 인스턴스를 생성시키지 못한다.
Animal animal = new Animal(); // (X)
추상 클래스는 새로운 실체 클래스를 만들기 위해 부모 클래스로만 사용된다. 코드로 설명하면 추상 클래스는 extends 뒤에만 올 수 있는 클래스이다. 예를 들어 Ant 클래스를 만들기 위한 Animal 클래스는 다음과 같이 사용할 수 있다.
Class Ant extends Animal { ... }
추상 클래스의 용도
실체 클래스들의 공통적인 특성을 뽑아내어 추상 클래스로 만드는 이유가 무엇일까?
첫 번째, 실체 클래스들의 공통된 필드와 메소드의 이름을 통일할 목적
실체 클래스를 설계하는 사람이 여러 사람일 경우, 실체 클래스마다 필드와 메소드가 제각기 다른 이름을 가질 수 있다. 예를 들어 소유자의 이름을 저장하는 필드를 Telephone에서는 owner라고 하고, SmartPhone에서는 User라고 할 수 있다. 그리고 전원을 켜다라는 메소드를 Telephone에서는 turnOn() 으로 설계하고, SmartPhone에서는 PowerOn()이라고 설계할 수 있다. 동일한 데이터와 기능임에도 불구하고 이름이 다르다 보니, 객체마다 사용 방법이 달라진다. 이것 보다는 Phone이라는 추상 클래스에 소유자인 owner 필드와 turnOn() 메소드를 선언하고, Telephone과 SmartPhone은 Phone을 상속함으로써 필드와 메소드 이름을 통일시킬 수 있다.
두번째, 실체 클래스를 작성할 때 시간을 절약
공통적인 필드와 메소드는 추상 클래스인 Phone에 모두 선언해 두고, 실체 클래스마다 다른 점만 실체 클래스에 선언하게 되면 실체 클래스를 작성하는데 시간을 절약할 수 있다. Telephone과 SmartPhone은 Phone을 상속받기 때문에 owner 필드와 turnOn() 메소드를 선언할 필요가 없다.
추상 클래스 선언
추상 클래스를 선언할 때에는 클래스 선언에 abstract 키워드를 붙여야 한다. abstract를 붙이게 되면 new 연산자를 이용해서 객체를 만들지 못하고 상속을 통해서 자식 클래스만 만들 수 있다.
public abstract class 클래스 {
// 필드
// 생성자
// 메소드
}
추상 클래스도 일반 클래스와 마찬가지로 필드, 생성자, 메소드 선언을 할 수 있다. new 연산자로 직접 생성자를 호출할 수는 없지만 자식 객체가 생성될 때 super( ... )를 호출해서 추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 한다. 다음은 Phone 클래스를 추상 클래스로 선언한 것이다.
// Phone.java
public abstract class Phone {
// 필드
public String owner;
// 생성자
public Phone(String owner) {
this.owner = owner;
}
// 메소드
public void turnOn() {
System.out.println("폰 전원을 켭니다.");
}
public void turnOff() {
System.out.println("폰 전원을 끕니다.");
}
}
// SmartPhone.java
public class SmartPhone extends Phone{
// 생성자
public SmartPhone(String owner) {
super(owner);
}
// 메소드
public void internetSearch() {
System.out.println("인터넷 검색을 합니다.");
}
}
// PhoneExample.java
public class PhoneExample {
public static void main(String[] args) {
SmartPhone smartPhone = new SmartPhone("홍길동");
smartPhone.turnOn(); // Phone의 메소드
smartPhone.internetSearch();
smartPhone.turnOff(); // Phone의 메소드
}
}
추상 메소드와 오버라이딩
추상 클래스는 실체 클래스가 공통적으로 가져야 할 필드와 메소드들을 정의해 놓은 추상적인 클래스이므로 실체 클래스의 멤버를 동일화하는데 목적이 있다. 모든 실체들이 가지고 있는 메소드의 실행 내용이 동일하다면 추상 클래스에 메소드를 작성하는 것이 좋을 것이다. 하지만 메소드의 선언만 통일화하고, 실행 내용은 실체 클래스마다 달라야 하는 경우가 있다.
예를 들어, 모든 동물은 소리를 내기 때문에 Animal 추상 클래스에서 sound()라는 메소드를 정의했다고 하자. 그렇다면 어떤 소리를 내도록 해야 하는데, 이것은 실체에서 직접 작성해야 될 부분임을 알게 된다. 왜냐하면 동물은 다양한 소리를 내기 때문에 이것을 추상 클래스에서 통일적으로 작성할 수 없기 때문이다. 그렇다고 해서 sound() 메소드를 실체에서 작성하도록 하면 sound() 메소드를 잊어버리고 작성하지 않을 수도 있기 때문에 동물은 소리를 낸다는 것에 위배된다.
이런 경우를 위해서 추상 클래스는 추상 메소드를 선언할 수 있다. 추상 메소드는 추상 클래스에서만 선언할 수 있는데, 메소드의 선언부만 있고 메소드 실행 내용인 중괄호 { } 가 없는 메소드를 말한다. 추상 클래스를 설계할 때, 하위 클래스가 반드시 실행 내용을 채우도록 강요하고 싶은 메소드가 있을 경우, 해당 메소드를 추상 메소드로 선언하면 된다. 자식 클래스는 반드시 추상 메소드를 오버라이딩해서 실행 내용을 작성해야 하는데, 그렇지 않으면 컴파일 에러가 난다. 다음은 추상 메소드를 선언하는 방법을 보여준다.
[public | protected] abstract 리턴타입 메소드명(매개변수, ...);
일반 메소드 선언과의 차이점은 abstract 키워드가 붙어 있고 메소드 중괄호 { } 가 없다. 다음은 Animal 클래스를 추상 클래스로 선언하고 sound() 메소드를 추상 메소드로 선언한 것이다.
public abstract class Animal {
public abstract void sound();
}
어떤 소리를 내는지는 결정할 수 없지만 동물은 소리를 낸다는 공통적인 특징이 있으므로 sound() 메소드를 추상 메소드로 선언했다. Animal 클래스를 상속하는 하위 클래스는 고유한 소리를 내도록 sound() 메소드를 오버라이딩 해야한다. 예를 들어 Dog는 "멍멍", Cat은 "야옹" 과 같은 소리를 내도록 Dog, Cat 클래스에서 sound() 매소드를 오버라이딩 해야한다.
'Java 길찾기 > 이것이 자바다' 카테고리의 다른 글
[Java] 인터페이스 (2) (0) | 2022.01.27 |
---|---|
[Java] 인터페이스 (1) (0) | 2022.01.26 |
[Java] 타입 변환과 다형성 (2) (0) | 2022.01.24 |
[Java] 타입 변환과 다형성 (1) (0) | 2022.01.21 |
[Java] 상속 (2) (0) | 2022.01.20 |