추상적이다라는 것은 구체적이지 않고 막역한 것을 뜻한다.
그렇다면 추상 클래스(abstract class)는 구체적이지 않은 클래스를 말한다.
추상 클래스는 항상 추상 메서드를 포함하는데, 추상 메서드는 구현 코드가 없다.
함수의 구현 코드가 없다는 것은 함수 몸체(body)가 없다는 뜻이다.
int add(int x, int y) {
return x + y; // { } 안의 내용이 함수 몸체
}
중괄호 { }로 감싼 부분을 함수의 구현부(implementation)이라 하는데, 이 부분이 없는 함수는 추상 함수(abstract function)이고, 자바에서는 추상 메서드(abstract method)라고 한다.
abstract int add(int x, int y);
추상 메서드는 위와 같이 선언만 하며, abstract 예약어를 사용한다. 또한 { } 대신 ; 을 사용한다.
참고로
int add(int x, int y) { };
이 메서드는 추상 메서드가 아니다. { }를 사용한 것만으로도 코드가 없을 뿐 메서드를 구현한 셈이다.
프로그램 개발에 있어 변수를 선언하고 제어문으로 로직을 만들어 기능을 구현하는 것들도 중요하지만, 어떻게 구현할지가 더 중요한데, 이를 개발 설계라 한다.
int add(int num1, int num2);
이 메서드의 선언부(declatration)를 보았을 때, 두 개의 정수를 입력받아 더해 그 결괏값을 반환하는 것을 유추할 수 있다.
함수의 선언부 즉 반환값, 함수 이름, 매개 변수를 정의한다는 것은 곧 그 함수의 역할이 무엇인지, 어떻게 구현해야 하는지를 정의한다는 뜻이다.
따라서 함수 몸체를 구현하는 것보다 중요한 것은 함수 선언부를 작성하는 것이다.
메서드를 선언한다는 것은 메서드가 해야 할 일을 명시해두는 것이다.
위와 같은 클래스 다이어그램이 있을 때 위에서부터 차례로 클래스 이름, 변수 이름, 메서드 이름을 쓰고,
추상 클래스와 추상 메서드는 기울임꼴로 표시한다.
이 다이어그램을 코드로 구현해보자.
[실습] 추상 클래스 구현하기
package abstractex;
public class Computer {
public void display(); // 오류 발생
public void typing(); // 오류 발생
public void turnOn() {
System.out.println("전원을 켭니다.");
}
public void turnOff() {
System.out.println("전원을 끕니다.");
}
}
위의 다이어그램에서 상위 클래스인 Computer를 코드로 구현하면 이와 같은데
여기에서 display()와 typing()이 오류가 발생한다. 이 오류를 해결할 수 있는 방법은 2가지가 있다.
1. body 작성
2. 추상메서드로 변경
그렇다면 오류 난 메서드를 추상 메서드로 변경하면
public abstract void display();
public abstract void typing();
이렇게 변경이 되는데, 여전히 메서드에는 오류가 남아있고, 추가로 클래스에도 오류가 난 것을 볼 수 있다.
그 이유는 추상 메서드가 속한 클래스를 추상 클래스로 선언하지 않았기 때문이다.
그렇다면 클래스 또한 추상 클래스로 선언해주면 더 이상 오류는 발생하지 않는다.
public abstract class Computer {
public abstract void display();
public abstract void typing();
Computer 클래스를 이와 같이 구현한 것은 Computer를 상속받는 클래스 중 turnOn()과 turnOff() 구현코드는 공통이고,
display()와 typing()은 하위 클래스에 따라 구현이 달라질 수 있어 Computer에서 구현하지 않고
하위 클래스에서 구현하도록 하기 위함이다.
즉, 상위 클래스에서는 하위 클래스도 공통으로 사용할 메서드를 구현하고, 하위 클래스마다 다르게 구현할 메서드는 추상 메서드로 선언해 두는 것이다.
이제 하위 클래스를 선언해 보자
[실습] 추상 클래스 상속받기
package abstractex;
public class DeskTop extends Computer { // 오류 발생
}
상속받은 DeskTop 클래스는 오류가 발생하는데,
원래 Computer는 추상 클래스로, 추상 클래스를 상속받은 클래스는 추상 클래스가 가진 메서드를 상속받는다.
따라서 상속받은 클래스는 추상 메서드를 포함하기 때문에 오류가 발생하고,
이를 해결하려면 추상 메서드를 모두 구현하던지, 아니면 DeskTop 클래스도 추상 클래스로 만들어야 한다.
즉, 추상 클래스를 상속받은 하위 클래스는 구현되지 않은 추상 메서드를 모두 구현해야 구체적인 클래스가 된다.
[실습] 추상 메서드 구현하기
package abstractex;
public class DeskTop extends Computer {
@Override
public void display() {
System.out.println("DeskTop display()"); //추상 메서드 몸체코드 작성
}
@Override
public void typing() {
System.out.println("DeskTop typing()"); //추상 메서드 몸체코드 작성
}
}
마찬가지로 NoteBook 클래스도 구현해 보자.
[실습] NoteBook 클래스 구현하기
package abstractex;
public abstract class NoteBook extends Computer {
@Override
public void display() {
System.out.println("NoteBook display()");
}
}
이 클래스에서는 상속받은 추상 메서드를 모두 구현하지 않고 display() 하나만 구현했다.
그러므로 NoteBook 클래스는 추상 메서드를 하나 가지고 있기 때문에 추상 클래스가 된다.
[실습] MyNoteBook 클래스 구현하기
package abstractex;
public class MyNoteBook extends NoteBook {
@Override
public void typing() {
System.out.println("MyNoteBook typing()");
}
}
NoteBook 클래스를 상속받은 MyNoteBook 클래스는 모든 추상 메서드가 구현된 클래스이므로
abstract 예약어를 사용하지 않는다.
📌 만약 모든 추상 메서드를 구현한 클래스에 abstract 예약어를 사용한다면?
package abstractex;
public abstract class AbstractTV {
public void turnOn() {
System.out.println("전원을 켭니다.");
}
public void turnOff() {
System.out.println("전원을 끕니다.");
}
}
문법상으로 모든 메서드를 구현했어도 abstract 예약어를 사용하면 추상 클래스이다.
AbstractTV 클래스는 모든 추상 메서드를 구현한 클래스로, TV의 공통 기능만 구현해 놓은 것이다.
이 클래스는 생성해서 사용할 목적이 아닌 상속만을 위해 만든 추상 클래스이다.
이 경우 new 예약어로 인스턴스를 생성할 수 없다.
[실습] 추상 클래스 테스트하기
package abstractex;
public class ComputerTest {
public static void main(String[] args) {
Computer c1 = new Computer(); //클래스를 인스턴스로 생성할 수 없음
Computer c2 = new DeskTop();
Computer c3 = new NoteBook(); //클래스를 인스턴스로 생성할 수 없음
Computer c4 = new MyNoteBook();
}
}
Computer()와 NoteBook()은 바디에 구현된 코드가 없으므로 수행할 수 있는 내용이 없다.
바꿔 말하자면 추상 클래스는 인스턴스를 만들 수 없다.
하지만, 추상 클래스에서도 형 변환을 사용할 수는 있다.
DeskTop, NoteBook, MyNoteBook 클래스는 상위 클래스 Computer를 상속받았으므로, Computer 형으로 선언하여 사용이 가능하다.
그렇다면 생성할 수 없는 추상 클래스는 어디에 사용하는 것일까?
추상 클래스는 상속을 위해 만든 클래스로, 하위 클래스에서도 구현 내용을 공유할 메서드를 구현한다.
구현된 메서드 | 하위 클래스에서 공통으로 사용할 구현 코드. 하위 클래스에서 재정의할 수도 있음 |
추상 메서드 | 하위 클래스가 어떤 클래스냐에 따라 구현 코드가 달라짐 |
📌 추상 클래스와 프레임워크
실제 추상 클래스는 많은 프레임워크에서 사용하고 있는 구현 방식이다. 예를 들어 안드로이드를 생각해 보면, 안드로이드 앱을 만들 때 안드로이드 라이브러리에서 제공하는 많은 클래스를 사용한다. 이들 클래스 중에는 모두 구현된 클래스도 있지만, 일부만 구현되어 있어 상속을 받아 구현하는 경우도 있다.
이때 안드로이드에서 구현해 놓은 코드는 내부적으로 사용하거나 상속받은 모든 클래스가 공통으로 사용할 메서드이다. 그리고 구현을 미루어 놓은 메서드(추상 메서드)는 실제 앱에서 어떻게 만드느냐에 따라 다르게 구현해야 할 내용으로 앱에서 구현하도록 선언만 해둔 것이다.
[실습] 추상 클래스 구현하기 - 나 혼자 코딩 (288p)
다음은 Car 추상 클래스를 상속받은 Bus 클래스와 AutoCar 클래스를 표현한 다이어그램이다. CarTest.java 파일을 보고 유추하여 출력화면과 같이 출력되도록 Car, Bus, AutoCar 클래스를 직접 구현해 보세요.
package abstractex;
public class CarTest {
public static void main(String[] args) {
Bus bus = new Bus();
AutoCar autoCar = new AutoCar();
bus.run();
autoCar.run();
bus.refuel();
autoCar.refuel();
bus.takePassenger();
autoCar.load();
bus.stop();
autoCar.stop();
}
}
package abstractex;
public abstract class Car {
public abstract void run();
public abstract void refuel();
public void stop() {
System.out.println("차가 멈춥니다.");
}
}
package abstractex;
public class Bus extends Car {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
@Override
public void refuel() {
System.out.println("천연 가스를 충전합니다.");
}
public void takePassenger() {
System.out.println("승객을 버스에 태웁니다.");
}
}
package abstractex;
public class AutoCar extends Car {
@Override
public void run() {
System.out.println("차가 달립니다.");
}
@Override
public void refuel() {
System.out.println("휘발유를 주유합니다.");
}
public void load() {
System.out.println("짐을 싣습니다.");
}
}
'Language > Java' 카테고리의 다른 글
[Java] 다운 캐스팅과 instanceof (0) | 2022.12.20 |
---|---|
[Java] 다형성 활용하기 (0) | 2022.12.19 |
[Java] 다형성(polymorphism) (0) | 2022.12.15 |
[Java] 메서드 오버라이딩 (1) | 2022.12.13 |
[Java] 상속에서 클래스 생성과 형 변환 (0) | 2022.11.20 |