추상 클래스란?
객체를 직접 생성할 수 없는 클래스다. 객체 생성은 불가능하지만, 자식 클래스를 통해 간접적으로 객체를 만들 수 있다.
추상 클래스 형식
abstract 키워드를 사용해 선언한다. 일반 클래스와의 차이는 abstract 키워드가 추가된다는 점이다.
public abstract class Something{
// 내부 구현 생략..
}
언제 어떻게 사용할까?
어떤 객체가 스스로 존재할 수 없거나 존재하면 안 되는 경우 사용한다.
예를 들어, 다음과 같이 여러 클래스가 동일한 name 필드를 가지고 있다
class Tiger {
String name; // 중복필드 -> 상속
}
class Rabbit {
String name;
}
class Lion {
String name;
}
class Giraffe {
String name;
}
중복되는 필드를 부모 클래스 Animal로 빼내어 상속하면 코드가 더 간결해진다.
class Animal{
String name;
}
class Tiger extends Animal{} // -> 클래스를 상속
class Lion extends Animal{}
class Rabbit extends Animal{}
class Giraffe extends Animal{}
Tiger, Lion, Rabbit, Giraffe는 실제로 존재하는 동물이지만, "Animal"이라는 이름의 동물은 존재하지 않는다.
즉, "동물"이라는 개념 자체는 추상적이다.
마찬가지로, 객체지향에서도 "스스로 존재할 수 없는 객체"가 있다.
Animal 객체는 개별적으로 존재해서는 안 되고, Tiger, Lion, Rabbit, Giraffe 같은 구체적인 객체를 위해서만 존재해야 한다.
이럴 때 abstract 키워드를 사용하여 객체 생성을 막을 수 있다.
//Compile Error!
Animal a = new Animal();
추상 클래스는 스스로 객체화할 수 없다는 점을 기억해야 한다.
그렇다면 추상클래스를 왜 사용해야 할까?
상속과 인터페이스의 장점을 모두 가지고 있기 때문이다.
상속의 장점
인터페이스의 장점
추상클래스 예제
package com.fastcampus.de.java.study_abstract2.study_abstract;
abstract class Animal{
String name;
public Animal(String name) {
this.name = name;
}
public void eat(){
System.out.println(name + " is eating.");
}
abstract void makeSound();
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println(name + " says : Woof!");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println(name + " says Meow");
}
}
public class Main {
public static void main(String[] args) {
Dog d1 = new Dog("Buddy");
Cat c1 = new Cat("Kitty");
d1.eat();
d1.makeSound();
c1.eat();
c1.makeSound();
}
}
인터페이스 예제
interface Soundable{
void makeSound();
}
class Dog implements Soundable{
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
class Cat implements Soundable{
@Override
public void makeSound() {
System.out.println("Meow!");
}
}
public class Main {
public static void main(String[] args) {
Soundable dog = new Dog();
Soundable cat = new Cat();
dog.makeSound(); // Woof!
cat.makeSound(); // Meow!
}
}
추상 클래스 + 인터페이스 같이 사용하기
// 🚀 추상 클래스 (공통 기능 제공)
abstract class Vehicle {
String brand;
public Vehicle(String brand) {
this.brand = brand;
}
void startEngine() {
System.out.println(brand + " engine started!");
}
abstract void drive();
}
// 🎵 인터페이스 (추가 기능 제공)
interface Playable {
void playMusic();
}
// 🚗 Car 클래스 (Vehicle 상속 + Playable 구현)
class Car extends Vehicle implements Playable {
public Car(String brand) {
super(brand);
}
@Override
void drive() {
System.out.println(brand + " is driving.");
}
@Override
public void playMusic() {
System.out.println(brand + " is playing music.");
}
}
public class Main {
public static void main(String[] args) {
Car myCar = new Car("Tesla");
myCar.startEngine(); // Tesla engine started!
myCar.drive(); // Tesla is driving.
myCar.playMusic(); // Tesla is playing music.
}
}
문제: 직원 관리 시스템
문제 설명:
당신은 직원 관리 시스템을 개발하고 있습니다. 시스템은 각 직원의 기본적인 정보를 관리하며, 모든 직원은 work() 메서드를 통해 각자의 업무를 처리합니다. 그러나 직원마다 업무 처리 방식이 다릅니다.
모든 직원은 Employee라는 추상 클래스를 상속받습니다. 이 클래스는 name과 salary라는 필드와 work()라는 추상 메서드를 가집니다.
Employee 클래스를 상속받은 Manager와 Developer 두 개의 직급 클래스가 있습니다.
Manager 클래스는 work() 메서드를 오버라이드하여 "팀을 관리합니다."라는 메시지를 출력합니다.
Developer 클래스는 work() 메서드를 오버라이드하여 "코드를 작성합니다."라는 메시지를 출력합니다.
각 Employee 객체는 그들의 직급에 맞는 업무를 출력해야 합니다.
Task라는 인터페이스가 있으며, 모든 직원은 Task 인터페이스를 구현하여, taskName이라는 메서드를 통해 현재 수행하는 업무의 이름을 출력합니다.
Manager는 taskName()을 "팀 관리"로, Developer는 "개발"로 출력합니다.
Main 클래스에서 Manager와 Developer 객체를 생성한 뒤, 각각 work()와 taskName() 메서드를 호출하여 결과를 출력하세요.
기본 요구 사항:
1. Employee 추상 클래스와 Manager, Developer 클래스 구현
2. Task 인터페이스 구현
3. work() 메서드를 각 클래스에서 다르게 구현
4. taskName() 메서드를 각 클래스에서 다르게 구현
5. Main 클래스에서 다형성을 이용해 각각의 객체에서 메서드를 호출하고 출력하기
수정 전 코드
// 직원 클래스
class Manager {
String name;
double salary;
void work() {
System.out.println(name + " is managing the team.");
}
void taskName() {
System.out.println("Task: Managing the team.");
}
}
class Developer {
String name;
double salary;
void work() {
System.out.println(name + " is writing code.");
}
void taskName() {
System.out.println("Task: Writing code.");
}
}
public class Main {
public static void main(String[] args) {
Manager manager = new Manager();
manager.name = "Alice";
manager.salary = 5000;
Developer developer = new Developer();
developer.name = "Bob";
developer.salary = 4000;
// 직무 수행
manager.work();
manager.taskName();
developer.work();
developer.taskName();
}
}
수정 후 요구 사항:
수정 후 코드
package com.fastcampus.de.java.study_abstract;
// Task 인터페이스 정의
interface Task{
void taskName();
}
// Employee 추상 클래스 정의
abstract class Employee{
String name;
double salary;
// 공통된 업무 메서드는 추상 메서드로 선언
abstract void work();
}
class Manager extends Employee implements Task{
@Override
void work() {
System.out.println(name + " is managing the team.");
}
@Override
public void taskName() {
System.out.println("Task: Managing the team.");
}
}
class Developer extends Employee implements Task{
@Override
void work() {
System.out.println(name + " is writing code.");
}
@Override
public void taskName() {
System.out.println("Task: Writing code.");
}
}
public class Main {
public static void main(String[] args) {
Employee manager = new Manager();
manager.name = "Alice";
manager.salary = 5000;
Employee developer = new Developer();
developer.name = "Bob";
developer.salary = 4000;
// 직무 수행 및 업무 이름 출력
manager.work();
((Task) manager).taskName(); // Task 인터페이스 메서드 호출
developer.work();
((Task)developer).taskName(); // Task 인터페이스 메서드 호출
}
}