자바는 객체 지향 프로그래밍 언어로, 객체 지향의 핵심 원칙 중 하나는 '추상화'입니다. 추상화는 복잡한 현실 세계를 단순화하고 모델링하는 프로세스를 의미하며, 자바에서는 이러한 추상화를 위해 인터페이스와 추상 클래스라는 두 가지 주요 개념을 제공합니다. 이 글에서는 자바의 인터페이스와 추상 클래스에 대해 자세히 알아보고, 각각의 특징과 사용 사례를 알아보겠습니다.
인터페이스 (Interface)
인터페이스의 정의와 역할
인터페이스는 추상화의 한 형태로, 메서드와 상수만을 가질 수 있는 참조 타입입니다. 이 메서드는 실제로 구현되지 않고 메서드의 시그니처(메서드 이름, 매개변수 목록, 반환 형식)만 정의됩니다. 인터페이스는 클래스와 달리 멤버 변수를 가질 수 없으며, 모든 필드는 상수로 취급됩니다.
인터페이스의 역할
▶ 메서드 강제 구현
인터페이스는 그 안에 정의된 메서드를 반드시 구현해야 하는 규약입니다. 클래스에서 인터페이스를 구현하면 해당 클래스는 인터페이스의 메서드를 모두 구현해야 합니다. 인터페이스를 통해 메서드의 시그니처를 강제함으로써, 구현 클래스가 일관된 동작을 보장합니다.
▶ 다형성 지원
인터페이스를 구현하는 클래스는 해당 인터페이스 타입으로 취급될 수 있습니다. 이를 통해 다양한 구현체를 동일한 방식으로 처리할 수 있습니다. 이것은 코드의 유연성을 높여주며, 다형성을 구현할 수 있는 중요한 도구입니다.
인터페이스를 선언하고 구현하는 방법
인터페이스는 interface 키워드를 사용하여 선언하며, 클래스가 인터페이스를 구현하기 위해서는 implements 키워드를 사용합니다.
인터페이스 선언
인터페이스는 interface 예약어를 사용하여 선언합니다. 아래는 간단한 인터페이스의 예시입니다.
public interface Drawable {
void draw();
}
:: 위의 코드는 Drawable라는 이름의 인터페이스를 선언하고 있습니다. 이 인터페이스는 draw 메서드를 가지고 있습니다.
인터페이스 구현
인터페이스를 구현하는 클래스는 implements 예약어를 사용합니다. 아래는 Drawable 인터페이스를 구현하는 클래스의 예시입니다.
public class Circle implements Drawable {
@Override
public void draw() {
System.out.println("Circle is drawn");
}
}
:: 위 코드에서 Circle 클래스는 Drawable 인터페이스의 draw 메서드를 구현하고 있습니다.
다중 상속을 지원하는 인터페이스의 활용
자바에서는 클래스는 다중 상속을 지원하지 않습니다. 그러나 인터페이스를 이용하면 다중 상속을 구현할 수 있습니다. 클래스는 여러 개의 인터페이스를 구현할 수 있으므로 다양한 기능을 조합하여 사용할 수 있습니다. 이는 자바에서 코드의 재사용성을 높이는 중요한 방법 중 하나입니다.
아래는 다중 인터페이스 구현의 예시입니다.
public interface Shape {
double calculateArea();
}
public interface Colorable {
void setColor(String color);
}
public class Circle implements Shape, Colorable {
private double radius;
private String color;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public void setColor(String color) {
this.color = color;
}
}
:: 위 코드에서 Circle 클래스는 Shape 인터페이스와 Colorable 인터페이스를 동시에 구현하고 있습니다. 따라서 원의 면적을 계산하고 색상을 설정하는 기능을 모두 갖추고 있습니다.
인터페이스의 메서드
추상 메서드와 상수 필드의 개념
▶ 추상 메서드 (Abstract Method)
인터페이스의 메서드는 구체적인 구현 내용이 없는 추상 메서드입니다. 추상 메서드는 메서드의 시그니처(메서드 이름, 매개변수 목록, 반환 형식)만 정의하고, 실제 동작은 하위 클래스에서 구현해야 합니다. 이렇게 하위 클래스에서 메서드를 강제로 구현하도록 하는 것이 인터페이스의 역할 중 하나입니다.
▶ 상수 필드 (Constants)
인터페이스는 상수 필드를 가질 수 있습니다. 이러한 상수는 public static final 키워드를 사용하여 선언되며, 인터페이스 내에서는 값을 변경할 수 없습니다. 상수 필드는 주로 인터페이스에서 사용할 상수 값을 정의하기 위해 활용됩니다.
인터페이스에서 메서드 선언과 구현
1. 메서드 선언
인터페이스 내에서 메서드는 다음과 같이 선언됩니다.
void methodName(parameterType parameterName);
:: 여기서 void는 반환 형식을 나타내며, methodName은 메서드 이름, parameterType은 매개변수의 데이터 타입, parameterName은 매개변수의 이름입니다. 메서드 선언만으로는 구체적인 구현 내용이 없으며, 하위 클래스에서 이 메서드를 구현해야 합니다.
2. 메서드 구현
하위 클래스에서 인터페이스의 메서드를 구현할 때는 @Override 어노테이션을 사용하여 상위 인터페이스의 메서드를 재정의합니다. 메서드의 시그니처는 반드시 일치해야 합니다.
다음은 Drawable 인터페이스의 draw 메서드를 구현하는 클래스의 예시입니다.
public class Circle implements Drawable {
@Override
public void draw() {
// 구체적인 그리기 로직을 작성합니다.
System.out.println("Circle is drawn");
}
}
인터페이스를 구현하는 클래스의 메서드 구현
인터페이스를 구현하는 클래스에서는 모든 인터페이스 메서드를 구현해야 합니다. 이렇게 하면 해당 클래스는 해당 인터페이스의 규약을 준수하게 됩니다. 또한 클래스 내에서 다른 메서드를 추가로 구현할 수도 있습니다.
다음은 Drawable 인터페이스를 구현하는 Circle 클래스의 예시입니다.
public class Circle implements Drawable {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public void draw() {
// 원을 그리는 로직
System.out.println("Drawing a circle with radius " + radius);
}
public double calculateArea() {
// 원의 면적을 계산하는 로직
return Math.PI * radius * radius;
}
}
:: 위 코드에서 calculateArea 메서드는 Drawable 인터페이스의 draw 메서드 외에 추가로 정의된 메서드입니다.
자바 인터페이스의 메서드는 추상 메서드와 상수 필드로 구성되며, 인터페이스를 구현하는 클래스에서 이 메서드들을 구체적으로 구현해야 합니다. 이를 통해 객체 지향 프로그래밍의 다형성을 활용하고 코드의 재사용성을 높일 수 있습니다.
인터페이스의 다형성
인터페이스를 활용한 다양한 클래스의 상호 교환성
다형성(Polymorphism)
다형성은 객체 지향 프로그래밍에서 중요한 원칙 중 하나로, 다양한 객체를 동일한 인터페이스나 부모 클래스로 다룰 수 있게 해 줍니다. 이것은 코드를 더 추상적으로 작성하고, 객체 간의 상호 교환성을 확보하는 데 도움이 됩니다.
:: 인터페이스는 추상 메서드와 상수 필드로 이루어진 일종의 계약(Contract)입니다. 인터페이스를 구현하는 클래스는 인터페이스에서 정의한 메서드를 모두 구현해야 합니다. 이로써 인터페이스를 구현한 클래스들은 동일한 메서드 시그니처를 공유하게 되며, 이것이 다형성을 가능하게 합니다.
예시 코드: Drawable 인터페이스로 구현하는 클래스의 다형성
Drawable 인터페이스
public interface Drawable {
void draw();
}
Drawable 인터페이스는 draw라는 메서드만 정의하고 있습니다. 이제 Drawable 인터페이스를 구현하는 다양한 클래스를 살펴보겠습니다.
Circle 클래스
public class Circle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
Rectangle 클래스
public class Rectangle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
Triangle 클래스
public class Triangle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing a triangle");
}
}
:: 위와 같이 각 클래스는 Drawable 인터페이스를 구현하고 있는데, 모두 draw 메서드를 다양한 방식으로 구현하고 있습니다.
인터페이스를 이용한 코드의 유연성 증진
코드의 유연성
인터페이스를 사용하면 코드의 유연성이 증진됩니다. 왜냐하면 클라이언트 코드에서는 구체적인 클래스를 인스턴스화하는 대신 인터페이스를 사용하여 객체를 다룰 수 있기 때문입니다. 이로 인해 코드 변경이나 확장이 더 쉬워지며, 유지보수가 용이해집니다.
예시 코드: 그림 그리기 애플리케이션
public class DrawingApp {
public static void main(String[] args) {
Drawable circle = new Circle();
Drawable rectangle = new Rectangle();
Drawable triangle = new Triangle();
// 다양한 도형을 그릴 수 있음
drawShape(circle);
drawShape(rectangle);
drawShape(triangle);
}
public static void drawShape(Drawable shape) {
shape.draw();
}
}
:: 위 코드에서 DrawingApp 클래스는 다양한 도형을 그리는데 Drawable 인터페이스를 활용합니다. 이를 통해 새로운 도형 클래스가 추가되더라도 DrawingApp의 변경 없이 쉽게 적용할 수 있습니다.
자바 인터페이스의 다형성은 코드의 유연성과 확장 가능성을 증진시키는 중요한 개념입니다. 인터페이스를 통해 다양한 클래스를 동일한 인터페이스로 다루고, 클라이언트 코드를 보다 추상적으로 작성함으로써 소프트웨어의 유지보수와 확장이 더 쉬워집니다.
추상 클래스 (Abstract Class)
추상 클래스는 일부 메서드가 구현되지 않은(추상 메서드) 클래스입니다. 이 클래스는 객체를 직접 생성할 수 없으며, 상속을 통해 하위 클래스에서 추상 메서드를 구현해야 합니다. 추상 클래스는 공통된 속성과 메서드를 가진 클래스들의 공통부분을 추상화하여 코드의 재사용성을 높이는 데 사용됩니다.
추상 클래스의 특징
1) 객체 생성 불가능: 추상 클래스는 직접 객체를 생성할 수 없습니다. 즉, new AbstractClass()와 같이 인스턴스를 생성할 수 없습니다.
2) 추상 메서드 포함: 추상 클래스는 추상 메서드를 포함할 수 있습니다. 추상 메서드는 메서드의 시그니처만 정의하고, 구체적인 구현은 하위 클래스에서 이루어집니다.
3) 일반 메서드 포함 가능: 추상 클래스는 추상 메서드 외에도 일반 메서드를 포함할 수 있습니다. 이 일반 메서드는 하위 클래스에서 변경 없이 사용하거나, 필요에 따라 오버라이딩할 수 있습니다.
추상 클래스를 선언하고 상속받는 방법
1. 추상 클래스 선언
추상 클래스는 abstract 예약어를 사용하여 선언됩니다. 아래는 추상 클래스의 예시입니다.
public abstract class Shape {
// 추상 메서드
public abstract void draw();
// 일반 메서드
public void resize() {
System.out.println("Resizing the shape");
}
}
:: 위 코드에서 Shape 클래스는 추상 메서드 draw와 일반 메서드 resize를 포함하고 있습니다.
2. 추상 클래스를 상속받는 클래스
추상 클래스를 상속받는 클래스는 extends 키워드를 사용하여 상속받습니다. 이 때, 하위 클래스는 추상 메서드를 반드시 구현해야 합니다. 아래는 Shape 클래스를 상속받는 Circle 클래스의 예시입니다.
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
:: 위 코드에서 Circle 클래스는 Shape 클래스를 상속받아 draw 메서드를 구현하고 있습니다.
추상 메서드와 일반 메서드의 혼합 사용
▷ 추상 메서드의 역할
추상 메서드는 하위 클래스에게 메서드의 구현을 강제합니다. 즉, 하위 클래스는 추상 메서드를 반드시 구현해야 합니다. 추상 메서드를 사용하면 여러 하위 클래스에서 공통된 메서드 시그니처를 가지는 메서드를 갖게 할 수 있습니다.
▷ 일반 메서드의 역할
일반 메서드는 추상 클래스 내에서 구현되어 있는 메서드로, 하위 클래스에서 변경 없이 사용하거나 필요에 따라 오버라이딩할 수 있습니다. 이를 통해 추상 클래스는 공통된 동작을 제공하면서도 유연성을 유지할 수 있습니다.
:: 위 예시 코드에서, Shape 클래스의 resize 메서드는 추상 메서드가 아니므로 하위 클래스에서 변경 없이 사용할 수 있습니다. 그러나 draw 메서드는 추상 메서드로 선언되어 있으므로 하위 클래스에서 반드시 구현해야 합니다.
추상 클래스의 활용
추상 클래스를 사용한 코드 재사용성
추상 클래스는 일부 메서드가 구현되지 않은 추상 메서드를 포함하며, 이러한 추상 메서드를 하위 클래스에서 구체적으로 구현하도록 강제합니다. 이로써 추상 클래스는 공통된 특성과 메서드를 추상화하여 다수의 서브클래스에서 공유하고 코드의 재사용성을 높입니다.
예시 코드: 도형 클래스
public abstract class Shape {
// 추상 메서드: 도형을 그리는 기능을 하위 클래스에서 구현
public abstract void draw();
// 공통 메서드: 모든 도형에서 공통으로 사용 가능
public void resize(int percentage) {
System.out.println("Resizing the shape by " + percentage + "%");
}
}
:: 위 코드에서 Shape 클래스는 draw라는 추상 메서드와 resize라는 공통 메서드를 포함하고 있습니다. draw 메서드는 구체적인 도형마다 다르게 구현되어야 하며, resize 메서드는 모든 도형에서 공통적으로 사용 가능합니다.
추상 메서드를 구현하는 서브클래스의 역할
추상 클래스를 상속받는 서브클래스는 추상 메서드를 반드시 구현해야 합니다. 이를 통해 하위 클래스는 추상 클래스에서 정의한 인터페이스(메서드 시그니처)를 따라야 하며, 이를 통해 코드 일관성과 다형성을 유지할 수 있습니다.
예시 코드: 원 클래스
public class Circle extends Shape {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Drawing a circle with radius " + radius);
}
}
:: 위 코드에서 Circle 클래스는 Shape 클래스를 상속받고 draw 메서드를 구체적으로 구현하고 있습니다.
추상 클래스와 인터페이스의 차이점
추상 클래스와 인터페이스는 모두 다형성을 구현하는데 도움을 주지만, 몇 가지 중요한 차이점이 있습니다.
▶ 구현 여부
추상 클래스는 일부 메서드를 구현한 클래스이므로 객체 생성이 불가능하며, 하위 클래스에서 추상 메서드를 반드시 구현해야 합니다. 반면 인터페이스는 모든 메서드가 추상 메서드이며, 모든 메서드를 구현해야 하는 것이 아니므로 다중 상속이 가능합니다.
▶ 목적
추상 클래스는 클래스 간의 공통된 특성을 추상화하여 코드를 재사용하고, 메서드의 일부 구현을 공유하기 위한 목적으로 사용됩니다. 반면 인터페이스는 클래스가 특정 인터페이스를 따르도록 강제하고, 다형성을 활용하기 위한 목적으로 사용됩니다.
인터페이스 vs. 추상 클래스
자바에서는 인터페이스(Interface)와 추상 클래스(Abstract Class)라는 두 가지 방법으로 추상화와 코드 재사용성을 구현할 수 있습니다. 두 개념을 비교하여 어떤 상황에서 어떤 것을 선택해야 하는지 살펴보겠습니다.
1. 다중 상속 지원 여부
▶ 인터페이스와 다중 상속
인터페이스는 자바에서 다중 상속을 지원하는 방법 중 하나입니다. 클래스는 단일 상속만을 허용하기 때문에, 클래스가 이미 어떤 클래스를 상속하고 있는 상황에서 다른 클래스를 상속받을 수 없습니다. 그러나 인터페이스는 여러 개를 동시에 구현할 수 있으므로, 다중 상속을 흉내 내는 방법으로 사용됩니다.
▷ 추상 클래스와 다중 상속
추상 클래스는 클래스 상속을 사용하는데, 클래스 상속은 단일 상속만을 지원합니다. 따라서 이미 어떤 클래스를 상속하고 있는 경우, 다른 클래스를 추가로 상속받을 수 없습니다. 이로 인해 다중 상속을 구현하기 어려울 수 있습니다.
2. 추상화 수준
▶ 인터페이스의 추상화 수준
인터페이스는 매우 높은 수준의 추상화를 제공합니다. 인터페이스는 메서드 시그니처만을 정의하고, 구체적인 구현 내용을 제공하지 않습니다. 이는 다양한 클래스가 동일한 메서드 시그니처를 공유하도록 하고, 그들이 필요에 따라 해당 메서드를 다르게 구현할 수 있도록 합니다.
▷ 추상 클래스의 추상화 수준
추상 클래스는 인터페이스보다는 낮은 수준의 추상화를 제공합니다. 추상 클래스는 일부 메서드를 구현할 수 있기 때문에, 하위 클래스에서 공통된 기능을 상속받을 수 있습니다. 이는 추상 클래스가 조금 더 구체적인 공통 동작을 제공하고, 하위 클래스에게 일부 구현을 강제할 수 있음을 의미합니다.
3. 코드 재사용성
▶ 인터페이스와 코드 재사용성
인터페이스는 다양한 클래스가 동일한 메서드 시그니처를 공유하도록 하는 데 중점을 두어 코드의 재사용성을 극대화합니다. 다수의 클래스가 동일한 인터페이스를 구현할 수 있으므로, 코드를 변경하지 않고도 새로운 클래스를 추가하고 기존 코드와 상호 작용할 수 있습니다.
▷ 추상 클래스와 코드 재사용성
추상 클래스는 공통된 기능을 구현할 수 있으므로, 하위 클래스가 이를 상속받아 재사용할 수 있습니다. 이는 공통된 로직을 추상 클래스에 두고, 다양한 하위 클래스에서 필요에 맞게 확장할 수 있음을 의미합니다. 따라서 추상 클래스도 코드 재사용성을 높이는 데 기여합니다.
어떤 것을 선택해야 할까?
인터페이스를 선택해야 할 때,
1) 다중 상속이 필요한 경우.
2) 클래스 간에 공통된 메서드 시그니처를 제공하고, 다양한 클래스가 해당 인터페이스를 구현할 수 있어야 하는 경우.
3) 다른 클래스와의 강력한 결합을 피하고자 하는 경우.
추상 클래스를 선택해야 할 때,
1) 공통된 로직을 구현하고, 하위 클래스에서 이를 재사용하고 확장해야 하는 경우.
2) 단일 상속이 필요하며, 클래스 간에 강한 연관성을 필요로 하는 경우.
3) 클래스의 일부 메서드를 미리 구현하여 하위 클래스에서 변경 가능한 동작을 제공해야 하는 경우.
:: 인터페이스는 다중 상속을 지원하고 높은 수준의 추상화를 제공하여 코드의 재사용성을 높이는 데 중점을 두며, 추상 클래스는 상속과 구체적인 구현을 통해 코드의 재사용성을 높이는 데 활용됩니다. 선택은 프로젝트의 목적과 설계에 따라 달라질 것이므로, 상황을 고려하여 적절한 방법을 선택하세요.
자바에서 인터페이스와 추상 클래스는 객체 지향 프로그래밍을 구현하고 코드의 재사용성, 일관성, 다형성을 증진시키는 데 중요한 역할을 합니다. 각각의 장단점과 사용 시나리오를 고려하여 선택해야 하며, 어떤 것을 사용해야 할지는 프로젝트의 요구사항과 클래스 계층 구조를 고려하여 결정해야 합니다.
'# Coding > Java' 카테고리의 다른 글
| 자바 응용 계산기 프로그램 (1) | 2023.11.21 |
|---|---|
| 자바 람다식 (1) | 2023.10.12 |
| 자바 내부클래스 익명클래스 (1) | 2023.09.25 |
| 자바 상속 (0) | 2023.09.25 |
| 자바 생성자 소멸자 this 키워드 (0) | 2023.09.23 |