1. 추상 클래스 (Abstract Class)란?
추상 클래스란 클래스의 설계도를 제공하지만 완전하지 않은 클래스입니다. 즉, 일부 메서드는 구현되어 있지만, 구체적인 내용을 정의하지 않은 추상 메서드를 포함할 수 있습니다. 추상 클래스는 직접 객체를 생성할 수 없으며, 상속을 통해 자식 클래스에서 구체적으로 구현해야 합니다.
1.1 추상 클래스 정의
- 추상 클래스는
abstract
키워드를 사용하여 선언합니다. - 추상 메서드도
abstract
키워드를 사용하며, 해당 메서드는 내용 없이 메서드 선언만 있습니다. - 자식 클래스에서 추상 메서드를 반드시 구현해야 합니다.
2. 추상 클래스의 특징
2.1 추상 클래스는 객체를 생성할 수 없다.
추상 클래스는 불완전한 클래스이므로 직접적으로 객체를 생성할 수 없습니다. 추상 메서드가 구현되지 않았기 때문에 객체를 만들면 그 객체는 "어떻게 동작할지" 명확하지 않기 때문입니다. 자식 클래스를 통해 상속받은 자식 클래스에서 객체를 생성해야 합니다.
// 추상 클래스
abstract class Animal {
abstract void sound(); // 추상 메서드
}
public class Main {
public static void main(String[] args) {
// 추상 클래스를 통해 객체를 직접 생성하려 하면 컴파일 오류 발생
// Animal animal = new Animal(); // 오류!
}
}
2.2 추상 클래스는 추상 메서드를 가질 수 있다.
추상 클래스는 추상 메서드를 포함할 수 있습니다. 추상 메서드는 메서드의 이름과 시그니처(매개변수와 반환값)만 선언하고, 구현은 하지 않으며 자식 클래스에서 반드시 구현해야 합니다.
// 추상 클래스 Animal
abstract class Animal {
// 추상 메서드 - 자식 클래스가 반드시 구현해야 함
abstract void sound();
}
// 추상 클래스를 상속 받는 Dog 클래스
class Dog extends Animal {
// sound() 메서드를 구현하지 않음
// 컴파일 오류 발생
// Error: Dog is not abstract and does not override abstract method sound() in Animal
}
// 추상 클래스를 상속 받는 Cat 클래스
class Cat extends Animal {
// 추상 메서드 구현
@Override
void sound() {
System.out.println("야옹");
}
}
컴파일 오류가 발생하는데 이것은 Dog
클래스가 Animal
클래스의 추상 메서드 sound()
를 구현하지 않았기 때문에 발생합니다. 추상 메서드를 반드시 구현해야 한다는 규칙을 따르지 않았기 때문에, 컴파일러가 오류를 발생시키는 것입니다.
2.3 일반 메서드도 가질 수 있다.
추상 클래스는 추상 메서드뿐만 아니라 구현된 메서드도 가질 수 있습니다. 이 구현된 메서드는 자식 클래스에 상속되어 동일하게 사용됩니다.
// 추상 클래스 Animal
abstract class Animal {
abstract void sound();
// 일반 메서드
void breathe() {
System.out.println("동물이 숨을 쉰다.");
}
}
class Dog extends Animal {
void sound() {
System.out.println("멍멍");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound(); // 출력: 멍멍 (추상 메서드 구현)
dog.breathe(); // 출력: 동물이 숨을 쉰다. (추상 클래스의 일반 메서드 사용)
}
}
2.4 추상 클래스를 상속받은 자식 클래스는 추상 메서드를 구현해야 한다.
자식 클래스가 추상 클래스를 상속받으면, 추상 클래스에 정의된 모든 추상 메서드를 반드시 구현해야 합니다. 만약 자식 클래스가 추상 메서드를 모두 구현하지 않으면, 그 자식 클래스도 추상 클래스로 선언되어야 합니다. 즉, 추상 클래스를 상속받았지만, 추상 메서드를 구현하지 않은 클래스는 추상 클래스로 선언해야 하며, 직접 객체를 생성할 수 없습니다.
abstract class Animal {
abstract void sound(); // 추상 메서드
}
abstract class Bird extends Animal {
// 추상 메서드를 구현하지 않으면, Bird도 추상 클래스가 됨
// Bird 클래스는 추상 메서드를 구현하지 않았으므로 추상 클래스로 선언됨
}
class Sparrow extends Bird {
// 추상 메서드를 반드시 구현해야 함
@Override
void sound() {
System.out.println("짹짹");
}
}
public class Main {
public static void main(String[] args) {
Sparrow sparrow = new Sparrow();
sparrow.sound(); // 출력: 짹짹
// 추상 클래스를 통해 객체를 직접 생성하려 하면 컴파일 오류 발생
// Bird bird = new Bird(); // 오류!
}
}
3. 추상 클래스의 사용 목적
추상 클래스의 주요 목적은 공통적인 동작을 정의하고, 하위 클래스에서 구체적인 구현을 강제하기 위함입니다. 즉, 공통된 기능은 추상 클래스에서 미리 제공하고, 상세한 구현은 자식 클래스에서 다양하게 정의함으로써 유연성과 재사용성을 높일 수 있습니다.
3.1 코드 재사용
추상 클래스는 여러 자식 클래스에 공통적으로 적용될 수 있는 코드를 미리 구현해 놓을 수 있습니다. 이는 코드 중복을 줄이고 유지보수를 더 쉽게 만듭니다. 예를 들어, 다양한 데이터베이스 연결 방식에서 연결을 열고 닫는 기본적인 기능은 동일할 수 있습니다. 이때 추상 클래스에서 공통 기능을 제공하고, 각 데이터베이스에 맞는 커넥션 로직은 자식 클래스에서 구현하도록 할 수 있습니다.
// 추상 클래스: 데이터베이스 연결 관리
abstract class DatabaseConnector {
// 모든 데이터베이스에서 공통적으로 사용되는 메서드
void openConnection() {
System.out.println("데이터베이스 연결을 엽니다.");
}
// 모든 데이터베이스가 구현해야 하는 추상 메서드
abstract void connect();
// 공통적으로 사용되는 메서드
void closeConnection() {
System.out.println("데이터베이스 연결을 닫습니다.");
}
}
// MySQL 데이터베이스 연결 클래스
class MySQLConnector extends DatabaseConnector {
@Override
void connect() {
System.out.println("MySQL 데이터베이스에 연결합니다.");
}
}
// PostgreSQL 데이터베이스 연결 클래스
class PostgreSQLConnector extends DatabaseConnector {
@Override
void connect() {
System.out.println("PostgreSQL 데이터베이스에 연결합니다.");
}
}
public class Main {
public static void main(String[] args) {
DatabaseConnector db1 = new MySQLConnector();
DatabaseConnector db2 = new PostgreSQLConnector();
db1.openConnection(); // "데이터베이스 연결을 엽니다."
db1.connect(); // "MySQL 데이터베이스에 연결합니다."
db1.closeConnection(); // "데이터베이스 연결을 닫습니다."
db2.openConnection(); // "데이터베이스 연결을 엽니다."
db2.connect(); // "PostgreSQL 데이터베이스에 연결합니다."
db2.closeConnection(); // "데이터베이스 연결을 닫습니다."
}
}
- 공통 메서드
openConnection()
과closeConnection()
은 추상 클래스에 구현되어 모든 데이터베이스에서 재사용됩니다. - 구체적인 데이터베이스 연결 로직은 각 데이터베이스에 맞게 자식 클래스에서 구현됩니다
3.2 강제 구현
추상 클래스는 추상 메서드를 통해 자식 클래스가 특정 기능을 반드시 구현하도록 강제할 수 있습니다. 예를 들어, 결제 시스템에서 결제 처리 방식은 각 결제 수단마다 다를 수 있지만, 모든 결제는 반드시 processPayment()
메서드를 구현해야 합니다. 이렇게 하면 결제 수단마다 일관된 메서드 시그니처로 구현을 강제할 수 있습니다.
// 추상 클래스: 결제 처리 시스템
abstract class PaymentProcessor {
// 결제 처리 메서드는 반드시 구현해야 함
abstract void processPayment(double amount);
// 공통 메서드: 결제 완료 처리
void confirmPayment() {
System.out.println("결제가 완료되었습니다.");
}
}
// 신용카드 결제 처리 클래스
class CreditCardProcessor extends PaymentProcessor {
@Override
void processPayment(double amount) {
System.out.println(amount + " 원이 신용카드로 결제되었습니다.");
}
}
// 페이팔 결제 처리 클래스
class PayPalProcessor extends PaymentProcessor {
@Override
void processPayment(double amount) {
System.out.println(amount + " 원이 PayPal로 결제되었습니다.");
}
}
public class Main {
public static void main(String[] args) {
PaymentProcessor creditCard = new CreditCardProcessor();
PaymentProcessor payPal = new PayPalProcessor();
creditCard.processPayment(50000); // "50000 원이 신용카드로 결제되었습니다."
creditCard.confirmPayment(); // "결제가 완료되었습니다."
payPal.processPayment(30000); // "30000 원이 PayPal로 결제되었습니다."
payPal.confirmPayment(); // "결제가 완료되었습니다."
}
}
- 각 결제 수단(신용카드, PayPal)은
processPayment()
메서드를 구현해야 합니다. - 결제가 완료되면 모든 결제 수단에 동일하게 사용되는
confirmPayment()
메서드는 추상 클래스에서 제공됩니다.
3.3 유연한 구조
추상 클래스는 자식 클래스에서 구현될 구체적인 동작이 다를 수 있는 부분을 유연하게 설계할 수 있습니다. 예를 들어, 다양한 파일 형식 처리기에서 파일을 읽고 쓰는 방식은 다를 수 있습니다. 이때 추상 클래스를 사용해 파일 입출력의 기본 동작을 제공하고, 각 파일 형식에 맞는 구체적인 로직을 자식 클래스에서 구현하도록 할 수 있습니다.
// 추상 클래스: 파일 처리기
abstract class FileProcessor {
// 공통 메서드: 파일 열기
void openFile(String fileName) {
System.out.println(fileName + " 파일을 엽니다.");
}
// 추상 메서드: 파일 읽기
abstract void readFile();
// 추상 메서드: 파일 쓰기
abstract void writeFile(String data);
// 공통 메서드: 파일 닫기
void closeFile() {
System.out.println("파일을 닫습니다.");
}
}
// 텍스트 파일 처리 클래스
class TextFileProcessor extends FileProcessor {
@Override
void readFile() {
System.out.println("텍스트 파일을 읽습니다.");
}
@Override
void writeFile(String data) {
System.out.println("텍스트 파일에 '" + data + "'를 씁니다.");
}
}
// CSV 파일 처리 클래스
class CsvFileProcessor extends FileProcessor {
@Override
void readFile() {
System.out.println("CSV 파일을 읽습니다.");
}
@Override
void writeFile(String data) {
System.out.println("CSV 파일에 '" + data + "'를 씁니다.");
}
}
public class Main {
public static void main(String[] args) {
FileProcessor textFile = new TextFileProcessor();
FileProcessor csvFile = new CsvFileProcessor();
textFile.openFile("document.txt");
textFile.readFile(); // "텍스트 파일을 읽습니다."
textFile.writeFile("Hello!"); // "텍스트 파일에 'Hello!'를 씁니다."
textFile.closeFile(); // "파일을 닫습니다."
csvFile.openFile("data.csv");
csvFile.readFile(); // "CSV 파일을 읽습니다."
csvFile.writeFile("Data"); // "CSV 파일에 'Data'를 씁니다."
csvFile.closeFile(); // "파일을 닫습니다."
}
}
- 파일을 여는(
openFile()
)과 닫는(closeFile()
) 공통적인 동작은 추상 클래스에서 제공됩니다. - 각 파일 형식(Text, CSV)의 파일 읽기/쓰기 로직은 각각의 자식 클래스에서 구현되어 유연한 구조를 제공합니다.
4. 결론
추상 클래스는 공통된 기능의 재사용, 구현 강제, 유연한 구조 제공이라는 세 가지 핵심 목적으로 사용됩니다. 각 기능을 필요에 맞게 추상 클래스로 설계하여 코드 중복을 줄이고, 명확한 규격을 정의하며, 다양한 상황에서 유연하게 동작할 수 있는 설계를 구현할 수 있습니다.
'BackEnd > JAVA' 카테고리의 다른 글
[JAVA] try-with-resources 정리 (0) | 2025.01.19 |
---|---|
[JAVA] 자바 부모클래스 및 인터페이스 심화: 오버라이딩과 메서드 동작 (0) | 2025.01.17 |
[JAVA] 인터페이스 정리 (0) | 2025.01.12 |
[JAVA] 접근 지정자 (Access Modifiers) 정리 (0) | 2025.01.06 |
[JAVA] 자바에서 main 메서드가 static인 이유 (0) | 2025.01.06 |