자바에서의 추상화는 객체 지향 프로그래밍에서 중요한 개념입니다. 추상화는 복잡한 시스템을 단순화하고 모델링하는 과정을 의미합니다. 이를 통해 개발자는 구체적인 구현 세부 사항을 감추고 핵심 개념에 집중할 수 있습니다.
자바에서 추상화를 실현하기 위해 '추상 클래스'와 '인터페이스'를 사용할 수 있습니다. 이 둘은 구체적인 구현을 갖지 않는 추상적인 개념이며, 이를 상속받거나 구현함으로써 추상화를 활용할 수 있습니다.
아래는 추상 클래스와 인터페이스를 사용한 간단한 예시 코드입니다:
// 추상 클래스
abstract class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public abstract void sound(); // 추상 메서드
public void eat() {
System.out.println(name + "이(가) 먹는 중입니다.");
}
}
// 인터페이스
interface Flyable {
void fly(); // 추상 메서드
}
// 추상 클래스를 상속받고 추상 메서드를 구현한 구체 클래스
class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void sound() {
System.out.println(name + "이(가) 멍멍하고 짖습니다.");
}
}
// 인터페이스를 구현한 클래스
class Bird implements Flyable {
public void fly() {
System.out.println("새가 날개를 펄럭이며 날아갑니다.");
}
}
위의 코드에서 Animal 클래스는 추상 클래스로, sound() 메서드는 구현되지 않은 추상 메서드입니다. 이를 상속받은 Dog 클래스에서 sound() 메서드를 구체적으로 구현합니다.
또한, Flyable 인터페이스는 fly() 메서드를 추상 메서드로 정의하고 있습니다. 이를 구현한 Bird 클래스에서 fly() 메서드를 구현합니다.
이렇게 추상 클래스와 인터페이스를 활용하여 추상화를 구현하면, 상속과 구현을 통해 공통된 특성과 동작을 가진 객체들을 효율적으로 관리하고 다양한 객체들을 일관성 있게 다룰 수 있습니다.
Abstract Class
추상 클래스는 직접적으로 인스턴스화할 수 없습니다. 추상 클래스는 구체적인 구현이 없는 추상 메서드를 포함할 수 있기 때문에, 이를 상속받아 구체적인 클래스에서 추상 메서드를 구현하도록 하는 것이 일반적인 사용 방법입니다.
추상 클래스를 인스턴스화하려고 시도하면 컴파일 오류가 발생합니다. 추상 클래스의 주된 목적은 상속을 통해 구체적인 클래스들에게 공통된 특성을 상속해 주는 것입니다. 추상 클래스를 상속받은 구체 클래스에서는 추상 메서드를 구현하고, 필요에 따라 추상 클래스의 일반 메서드를 재정의할 수 있습니다.
따라서, 추상 클래스는 인스턴스화될 수 없지만, 추상 클래스를 상속받아 구현한 구체(Concrete) 클래스의 인스턴스를 생성할 수 있습니다.
추상 클래스는 반드시 추상 메서드를 포함할 필요는 없습니다. 추상 클래스는 추상 메서드를 포함할 수도 있고, 일반 메서드를 가질 수도 있습니다. 추상 메서드는 구현이 없는 추상화된 메서드이며, 서브클래스에서 구체적인 구현을 제공해야 합니다.
추상 클래스는 자체적으로 객체를 생성할 수 없기 때문에, 추상 클래스를 상속받은 구체 클래스에서 추상 메서드를 구현해야 합니다. 이를 통해 추상 클래스가 갖는 공통된 특성을 구현한 클래스의 인스턴스를 생성할 수 있습니다.
추상 클래스의 목적은 공통된 속성과 동작을 가진 클래스들을 그룹화하고, 코드의 재사용성과 일관성을 높이는 것입니다. 따라서 추상 클래스는 추상 메서드와 일반 메서드를 조합하여 필요에 따라 구현을 갖출 수 있습니다.
private로 선언된 추상 메서드는 접근 제어자의 충돌로 인해 상속받은 클래스에서 구현할 수 없습니다. private 접근 제어자는 해당 멤버를 외부로부터 완전히 숨기기 때문에 하위 클래스에서 접근할 수 없습니다.
따라서, private로 선언된 추상 메서드를 포함하는 추상 클래스를 정의하고, 해당 클래스를 상속받은 클래스를 객체화하는 코드는 아래와 같이 작성될 수 없습니다. 예시 코드로는 아래와 같이 private 접근 제어자를 갖는 추상 메서드가 포함된 추상 클래스를 보여드리겠습니다:
abstract class AbstractClass {
private abstract void abstractMethod(); // private로 선언된 추상 메서드
}
class ConcreteClass extends AbstractClass {
// 하위 클래스에서 추상 메서드를 구현할 수 없음
}
public class Main {
public static void main(String[] args) {
ConcreteClass instance = new ConcreteClass(); // 컴파일 오류 발생
}
}
위의 코드에서 AbstractClass는 private 접근 제어자를 갖는 abstractMethod()라는 추상 메서드를 선언합니다. 이를 상속받은 ConcreteClass에서는 해당 추상 메서드를 구현할 수 없으며, 컴파일 오류가 발생합니다. 따라서 ConcreteClass의 인스턴스를 생성할 수 없습니다.
만약 하위 클래스에서 추상 메서드를 구현해야 한다면, 접근 제어자를 private 대신 protected 또는 public으로 변경해야 합니다. 이렇게 하면 하위 클래스에서 해당 메서드를 오버라이딩하여 구체적인 구현을 제공할 수 있습니다.
abstract class AbstractClass {
protected abstract void abstractMethod(); // protected로 수정된 추상 메서드
}
class ConcreteClass extends AbstractClass {
protected void abstractMethod() {
System.out.println("ConcreteClass에서 추상 메서드를 구현했습니다.");
}
}
public class Main {
public static void main(String[] args) {
ConcreteClass instance = new ConcreteClass();
instance.abstractMethod(); // "ConcreteClass에서 추상 메서드를 구현했습니다." 출력
}
}
protected 접근 제어자는 상속 관계에서의 접근을 제어하기 위해 사용됩니다. protected로 선언된 멤버는 동일한 패키지 내의 클래스와 상속 관계에 있는 외부 패키지 클래스에서 접근할 수 있습니다.
이 경우 protected로 수정된 추상 메서드를 사용하는 이유는 다음과 같습니다:
1. 상속을 통한 확장성: 추상 클래스의 추상 메서드를 protected로 선언하면 하위 클래스에서 해당 메서드를 오버라이딩하여 구체적인 구현을 제공할 수 있습니다. 이를 통해 추상 클래스를 상속받은 여러 클래스에서 동일한 메서드를 사용하면서도 각각의 클래스에 맞는 특정 동작을 추가할 수 있습니다.
2. 캡슐화와 은닉: protected 접근 제어자를 사용하면 상위 클래스의 내부 구현을 하위 클래스에게 노출하지 않고도 상속 관계에서 메서드에 접근할 수 있습니다. 이는 캡슐화와 정보 은닉을 강화하고 클래스 간의 결합도를 낮추는 데 도움이 됩니다.
3. 유연한 확장성: protected 접근 제어자를 사용하여 하위 클래스에서 오버라이딩된 메서드를 호출하면, 추상 클래스의 구현이 변경되어도 하위 클래스는 영향을 받지 않고 동작할 수 있습니다. 이는 추상 클래스의 내부 구현을 변경해도 하위 클래스에 영향을 주지 않고 유연한 확장성을 제공합니다.
protected 접근 제어자는 클래스 간의 상속 관계에서 메서드와 필드에 접근할 수 있는 범위를 제공하여 객체 지향 프로그래밍의 상속 및 다형성 개념을 지원합니다.
Interface
인터페이스는 자바에서 추상화를 실현하기 위한 중요한 개념입니다. 인터페이스는 일련의 추상 메서드와 상수들의 집합으로 정의됩니다. 클래스와 달리 다중 상속을 지원하며, 구현을 갖지 않는 추상 메서드로만 이루어져 있습니다.
인터페이스는 다음과 같은 특징을 가지고 있습니다:
1. 추상 메서드: 인터페이스는 추상 메서드를 선언하며, 이들은 구현을 갖지 않습니다. 추상 메서드는 인터페이스를 구현하는 클래스에서 반드시 구체적인 구현을 제공해야 합니다.
2. 다중 상속: 클래스는 단일 상속만을 지원하지만, 인터페이스는 다중 상속을 지원합니다. 클래스는 하나의 클래스만 상속받을 수 있지만, 여러 인터페이스를 구현할 수 있습니다. 이를 통해 클래스는 여러 개의 인터페이스로부터 다양한 특성과 동작을 상속받을 수 있습니다.
3. 구현을 갖지 않는 메서드: 인터페이스의 추상 메서드는 구현을 갖지 않습니다. 대신, 해당 인터페이스를 구현하는 클래스에서 메서드의 구체적인 구현을 제공해야 합니다. 이를 통해 인터페이스는 클래스 간의 일관성 있는 메서드 시그니처를 보장합니다.
4. 상수: 인터페이스는 상수를 선언할 수 있습니다. 이는 인터페이스 내에서 사용되는 고정된 값들을 나타냅니다. 상수는 자동으로 `public static final`로 선언되며, 클래스에서 접근할 때는 인터페이스 이름을 통해 접근해야 합니다.
인터페이스는 코드의 재사용성, 다형성, 유연성을 증가시키고, 클래스 간의 결합도를 낮춰줍니다. 다양한 클래스들이 공통된 동작을 가지도록 정의된 인터페이스를 구현함으로써, 인터페이스를 통해 다양한 객체를 일관성 있게 다룰 수 있습니다.
아래의 코드는 여러 개의 인터페이스를 구현하는 다중 상속 클래스의 예시입니다:
// 인터페이스 1
interface Walkable {
void walk();
}
// 인터페이스 2
interface Swimmable {
void swim();
}
// 여러 개의 인터페이스를 구현하는 클래스
class Human implements Walkable, Swimmable {
public void walk() {
System.out.println("사람이 걷습니다.");
}
public void swim() {
System.out.println("사람이 수영합니다.");
}
}
// Human 클래스의 인스턴스 생성 및 사용
public class Main {
public static void main(String[] args) {
Human person = new Human();
person.walk(); // "사람이 걷습니다." 출력
person.swim(); // "사람이 수영합니다." 출력
}
}
위의 코드에서 Walkable과 Swimmable 두 개의 인터페이스가 정의되어 있습니다. 그리고 Human 클래스는 이러한 인터페이스들을 구현하는 클래스입니다.
Human 클래스는 Walkable과 Swimmable 인터페이스에서 정의된 메서드인 walk()와 swim()을 구현합니다. 각 메서드는 해당 동작을 구체적으로 정의하고 출력합니다.
Main 클래스에서는 Human 클래스의 인스턴스를 생성하고, 해당 인스턴스의 메서드를 호출하여 동작을 수행합니다. 출력 결과로는 "사람이 걷습니다."와 "사람이 수영합니다."가 출력됩니다.
이를 통해 여러 개의 인터페이스를 구현하는 클래스를 만들고, 해당 인터페이스에서 정의된 메서드를 구현하고 사용할 수 있음을 보여드렸습니다.
Absctract Method & Interface
추상 메서드와 인터페이스는 모두 추상화를 통해 객체 지향 프로그래밍에서 다형성과 재사용성을 제공하는 데 사용됩니다. 그러나 둘 사이에는 몇 가지 차이점이 있습니다.
1. 구현 여부: 추상 메서드는 추상 클래스 내에 선언되며, 구체적인 구현이 없는 메서드입니다. 추상 메서드는 상속받은 클래스에서 반드시 구체적인 구현을 제공해야 합니다. 반면에 인터페이스는 구현을 가지지 않는 추상 메서드의 집합입니다. 클래스가 인터페이스를 구현할 때, 모든 인터페이스의 추상 메서드를 구현해야 합니다.
2. 다중 상속: 자바에서 클래스는 단일 상속만을 지원합니다. 그러나 인터페이스는 다중 상속을 지원합니다. 클래스는 하나의 클래스만 상속받을 수 있지만, 여러 개의 인터페이스를 구현할 수 있습니다. 따라서, 인터페이스는 다양한 타입의 특성과 동작을 클래스에 추가할 수 있는 유연성을 제공합니다.
이제 간단한 코드 예제를 통해 추상 메서드와 인터페이스의 차이점을 비교해보겠습니다:
// 추상 클래스
abstract class Animal {
public abstract void sound(); // 추상 메서드
public void eat() {
System.out.println("동물이 먹는 중입니다.");
}
}
// 인터페이스
interface Flyable {
void fly(); // 추상 메서드
}
// Animal 클래스를 상속받은 구체 클래스
class Dog extends Animal {
public void sound() {
System.out.println("개가 짖습니다.");
}
}
// 인터페이스를 구현한 클래스
class Bird implements Flyable {
public void fly() {
System.out.println("새가 날개를 펄럭이며 날아갑니다.");
}
}
위의 코드에서 Animal 클래스는 추상 클래스로 선언되었고, sound() 메서드는 추상 메서드로 선언되었습니다. Dog 클래스는 Animal 클래스를 상속받아 sound() 메서드를 구현합니다.
반면에 Flyable 인터페이스는 fly() 메서드라는 추상 메서드를 정의합니다. Bird 클래스는 Flyable 인터페이스를 구현하여 fly() 메서드를 구현합니다.
이를 통해 추상 클래스는 상속을 통한 구현을 요구하고, 인터페이스는 클래스가 인터페이스를 구현하도록 요구하는 것을 알 수 있습니다.
아래의 코드는 특정 추상 클래스를 상속하고 특정 인터페이스를 구현하는 구체 클래스의 예시입니다:
// 추상 클래스
abstract class Animal {
public abstract void sound(); // 추상 메서드
public void eat() {
System.out.println("동물이 먹는 중입니다.");
}
}
// 인터페이스
interface Flyable {
void fly(); // 추상 메서드
}
// Animal 클래스를 상속받고 Flyable 인터페이스를 구현한 구체 클래스
class FlyingDog extends Animal implements Flyable {
public void sound() {
System.out.println("개가 짖습니다.");
}
public void fly() {
System.out.println("개가 날아갑니다.");
}
}
// FlyingDog 클래스의 인스턴스 생성 및 사용
public class Main {
public static void main(String[] args) {
FlyingDog dog = new FlyingDog();
dog.sound(); // "개가 짖습니다." 출력
dog.eat(); // "동물이 먹는 중입니다." 출력
dog.fly(); // "개가 날아갑니다." 출력
}
}
위의 코드에서 FlyingDog 클래스는 Animal 추상 클래스를 상속받고, Flyable 인터페이스를 구현합니다. 이 클래스는 sound() 메서드와 eat() 메서드를 Animal 클래스에서 상속받고, fly() 메서드를 Flyable 인터페이스에서 구현합니다.
Main 클래스에서 FlyingDog 클래스의 인스턴스를 생성하고, 해당 인스턴스를 사용하여 메서드를 호출할 수 있습니다.
'Java' 카테고리의 다른 글
Cloneable 인터페이스 (0) | 2024.04.08 |
---|---|
Class 클래스와 Constructor 클래스 (0) | 2024.04.08 |
String Class (0) | 2024.04.08 |
Call by Value / Call by Reference (0) | 2024.04.08 |
try-catch-finally (0) | 2024.04.08 |