추상 클래스
- 부모 클래스는 제공하지만, 실제 생성되면 안 되는 클래스를 추상 클래스라 한다.
- 실체인 인스턴스가 존재하지 않고, 상속을 목적으로 사용되면 부모 클래스 역할을 담당한다.
abstract class AbstractClass {...}
- 추상 클래스는 클래스를 선언할 때 앞에 추상이라는 의미와 `abstract` 키워드를 붙여주면 된다.
- 추상 클래스는 기존 클래스와 완전히 같다.
- 다만 `new AbstractClass()`와 같이 직접 인스턴스를 생성하지 못하는 제약이 추가된 것이다.
추상 메서드
- 부모 클래스를 상속 받는 자식 클래스가 반드시 오버라이딩 해야 하는 메서드
- 추상 클래스와 마찬가지로 부모에 선언되며 실체가 존재하지 않기 때문에 메서드 바디가 없다.
public abstract void sound();
- 추상 메서드는 선언할 때 메서드 앞에 추상이라는 의미의 `abstract` 키워드를 붙여준다.
- 추상 메서드가 하나라도 있는 클래스는 추상 클래스로 선언해야 한다.
- 그렇지 않으면 컴파일 오류가 발생한다
- 추상 메서드는 자식 클래스가 반드시 오버라이딩 해야 하기 때문에 메서드 바디 부분이 없다.
- 바디를 만들면 컴파일 오류가 발생한다
- 오버라이딩 하지 않으면 자식도 추상 클래스가 되어야 한다.
정리
- 추상 클래스 덕분에 실수로 존재하면 안 되는 인스턴스(위의 경우 `AbstractClass`)를 생성할 문제를 근본적으로 방지해 준다.
- 추상 메서드 덕분에 새로운 자식 클래스를 만들 때 실수로 메서드 오버라이딩을 하지 않을 문제를 근본적으로 방지해 준다.
순수 추상 클래스
- 모든 메서드가 추상 메서드인 추상 클래스
public abstract class AbstractAnimal {
public abstract void sound();
public abstract void move();
}
- 순수 추상 클래스는 실행 로직을 전혀 가지고 있지 않다.
- 단지 다형성을 위한 부모 타입으로써 껍데기 역할만 제공한다.
순수 추상 클래스의 특징
- 인스턴스를 생성할 수 없다
- 상속 시 자식은 모든 메서드를 오버라이딩 해야 한다.
- 주로 다형성을 위해 사용된다.
자바는 순수 추상 클래스를 더 편리하게 사용할 수 있도록 인터페이스라는 개념을 제공한다.
인터페이스
인터페이스는 `class`가 아니라 `interface`키워드를 사용한다.
public interface InterfaceAnimal {
void sound();
void move();
}
- public abstract 키워드를 생략할 수 있다.
- 인터페이스는 다중 구현(다중 상속)을 지원한다.
- 인터페이스를 상속받을 때는 `extends` 대신에 `implements`라는 구현이라는 키워드를 사용한다.
- 인터페이스는 그래서 상속이라 하지 않고 구현이라 한다.
인터페이스와 멤버 변수
public interface InterfaceAnimal {
public static final double MY_PI = 3.14;
//다음과 같이 public static final은 생략 가능.
double MY_PI = 3.14;
}
- 인터페이스 멤버 변수는 public static final이 모두 포함되었다고 간주된다
- 때문에 생략이 권장된다.
클래스, 추상 클래스, 인터페이스는 모두 같다!
- 클래스, 추상 클래스, 인터페이스는 프로그램 코드, 메모리 구조상 모두 똑같다.
- 모두 자바에서는 `.class`로 다뤄진다.
- 인터페이스를 작성할 때도 `.java`에 인터페이스를 정의한다.
상속 vs 구현
부모 클래스의 기능을 자식 클래스가 상속받을 때, 클래스는 상속받는다고 표현하지만, 부모 인터페이스의 기능을 자식이 상속받을 때는 인터페이스를 구현한다고 표현한다.
상속은 이름 그대로 부모의 기능을 물려받는 것이 목적이다. 하지만 인터페이스는 모든 메서드가 추상 메서드이다. 따라서 물려받을 수 있는 기능이 없고, 오히려 인터페이스에 정의한 모든 메서드를 자식이 오버라이딩 해서 기능을 구현해야 한다. 따라서 구현한다고 표현한다.
인터페이스를 사용하는 이유
모든 메서드가 추상 메서드인 경우 순수 추상 클래스를 만들어도 되고, 인터페이스를 만들어도 된다. 그런데 왜 인터페이스를 사용해야 할까?
제약
- 인터페이스를 구현하는 곳에서 인터페이스의 메서드를 반드시 구현해라는 규약(제약)을 주는 것이다.
- 순수 추상 클래스의 경우 누군가 그곳에 실행 가능한 메서드를 끼워 넣을 수 있다.
- 이렇게 되면 추가된 기능을 자식 클래스에서 구현하지 않을 수도 있고, 또 더는 순수 추상 클래스가 아니게 된다.
- 인터페이스는 모든 메서드가 추상 메서드이다. 따라서 이런 문제를 원천 차단한다.
다중 구현
- 자바에서 클래스 상속은 부모를 하나만 지정할 수 있다.
- 반면에 인터페이스는 부모를 여명 두는 다중 구현(다중 상속)이 가능하다.
자바 8의 `default` 메서드를 사용하면 인터페이스에서도 메서드를 구현할 수 있다. 하지만 이것은 예외적으로 아주 특별한 경우에만 사용해야 한다. 자바 9의 인터페이스의 `private` 메서드도 마찬가지다.
인터페이스의 다중 구현
클래스의 다중 상속은 불가능하지만 인터페이스의 다중 구현은 허용한 이유는 뭘까?
-> 인터페이스는 모두 추상 메서드로 이루어져 있기 때문이다.
인터페이스 자신은 구현을 가지지 않는다. 대신에 인터페이스르 구현하는 곳에서 해당 기능을 모두 구현해야 한다.
이러한 이유로 인터페이스는 다이아몬드 문제가 발생하지 않기에 다중 구현이 허용된다.
고찰
인터페이스에 대해 학습하면서 인터페이스에 멤버 변수를 선언하는 경우가 있을지 의문이 들었다.
인터페이스를 사용하는 주된 목적은 위에 기술한 바와 같이 인터페이스를 구현하는 클래스에서 무조건적으로 메서드를 오버라이딩 하게 함으로써 메서드 오버라이딩을 빼먹는 문제를 방지해 주는 것인데, 인터페이스에서 상수를 같이 관리할 일이 있을까 궁금했다.
관련된 자료를 찾아보니,
1. 해당 인터페이스의 메서드와 직접적으로 연관된 상수
2. 특정 프로토콜이나 API 명세를 정의할 때
위의 두 경우에는 예외적으로 인터페이스에 상수를 정의할 수 있지만, 인터페이스에 상수를 정의하는 것은 권장되지 않는 방법이라고 한다. 왜냐하면 인터페이스의 상수는 자동으로 항상 public이어서 캡슐화가 불가능해, 변경이 필요할 경우 모든 구현 클래스에 영향을 미치기 때문이다.
그렇다면 상수는 어디서 관리해야 할까?
1. enum 사용 (가장 권장)
2. 전용 상수 클래스 생성
3. 관련 도메인 클래스 내부에 상수 정의
보통 위의 3가지 방법으로 관리된다고 한다. enum을 학습하면 조금 더 확실하게 이해가 될 듯하다.
'Java' 카테고리의 다른 글
[Java, 예외처리] 예외처리 정리 (0) | 2024.10.29 |
---|---|
[Java, Object] Object 클래스 정리 (0) | 2024.10.26 |
[Java, 다형성] 다형성 정리 1 (0) | 2024.10.24 |
[Java, 상속] Java 상속 정리 (0) | 2024.10.23 |
[Java, final] final 정리 (0) | 2024.10.23 |