추상클래스와 인터페이스? 차이점은 무엇이며 언제 적절히 사용해야 할까요?
자세히 살펴 보도록 하겠습니다.
추상 클레스
추상클레스란?
일부 메서드가 추상 메서드로 선언된 클래스입니다.
추상 클레스에는 구현부가 있는 일반 메서드가 존재할 수 있습니다.
코드 예시)
abstract class Singer {
private String name;
abstract void sing(); // 추상 메서드
public String getName() { // 일반 메서드
return name;
}
public void setName(String name) {
this.name = name;
}
}
추상클래스의 특징
1. 클래스와 추상 메서드에 abstract 키워드를 사용합니다.
2. 로직이 있는 메서드와 추상 메서드를 모두 가질 수 있습니다.
3. 밀접하게 관련된 클래스 끼리 코드를 공유할 때 사용합니다.
공통된 기능이나 행위를 가지고 있을 때 추상 클래스를 사용하여 중복 코드를 줄이고 구조를 일관성 있게 유지할 수 있습니다.
4. public이 아닌 다른 접근 제어자를 사용할 때 사용가능합니다.
5. static , final 등을 사용할 수 없습니다.
인터페이스
인터페이스란?
인터페이스는 추상클래스와 비슷합니다. 추상클래스와 같이 추상 메서드를 가집니다.
하지만 구현부가 있는 일반 메서드가 존재할 수 없습니다.
모든 멤버 변수는 public static final 이며 생략 가능하다.
모든 메서드는 public abstract 며 생략 가능함.
단, static메서드와 디폴트 메서드는 예외로 생략이 불가능하다.
↓ 코드 예시 ↓
interface Sound {
String sound = null; // 상수, public static final String sound = null
void makeSound(); // 추상 메서드, public abstract void makeSound();
default void defaultMethod(){ // default 메서드
System.out.println("A default Method");
};
static void staticMethod(){ // static 메서드
System.out.println("A static Method");
};
}
인터페이스 특징
1. 인터페이스는 interface 키워드를 사용합니다.
2. 인스턴스를 생성할 수 없고 상수만 가질 수 있습니다.
3. 추상메서드의 접근 제한자는 public과 default만 가능합니다.
4. 메서드 구현부가 없는 추상 메서드를 가집니다.
(JDK 1.8 이상) 하지만, 디폴트 메소드, static 메서드를 가질 수 있습니다.
default 메서드란?
- 접근 제어자의 default 와 다른 개념입니다.
- 인터페이스에서 메서드 body 를 가지는 메서드입니다.
- 이미 작성된 인터페이스에서 공통된 기능을 추가하고자 할때 사용합니다.
- 인터페이스에 정의된 메소드들을 구체 클레스가 모두 override 해야 했다면
default 메서드는 구체클레스에서 override 없이 사용 가능합니다.
5. 다중 상속이 가능합니다.
↓ default 메서드와 static 메서드 사용 예시 ↓
public interface A {
void abstractMethod(); // 추상 메서드
default void defaultMethod(){ // 디폴트 메서드
System.out.println("A default Method");
};
static void staticMethod(){ // static 메서드
System.out.println("A static Method");
};
}
class B implements A{
@Override
public void abstractMethod() { // 추상 메서드를 override 함.
System.out.println("B abstractMethod");
}
public static void main(String[] args) {
B b = new B();
b.abstractMethod(); // B abstractMethod
b.defaultMethod(); // A default Method 디폴트 메서드를 override 하지 않고 사용
A.staticMethod(); // A static Method static 메서드를 override 하지 않고 사용
}
}
추상 클레스와 인터페이스의 상속
추상 클레스와 인터페이스의 상속에 대해 코드를 통해 비교해 보도록 하겠습니다.
추상 클레스의 단일 상속
상속을 받는 자식 클래스 입장에서 추상 클래스는 단 하나만 상속받을 수 있습니다.
추상 클래스를 상속받을 때는 부모 클래스에 정의된 추상 메서드를 반드시 구현해야 합니다.
↓ 코드 예시 ↓
1. 추상 클레스를 이용한 코드 작성
노래와 작곡을 잘하는 싱어송라이터를 표현하기 위한 코드입니다.
Singer, SongWriter 에 해당하는 추상 클레스를 작성했고, SingerSongWriter class 에게 상속해 사용하려 합니다.
abstract class Singer {
abstract void sing();
}
abstract class SongWriter {
abstract void songWriter();
}
class SingerSongWriter extends Singer {
@Override
public void sing() { // 추상 클래스 Singer 의 sing 메서드를 구현.
System.out.println("노래 잘해용");
}
public static void main(String[] args) {
SingerSongWriter singerSongWriter = new SingerSongWriter();
singerSongWriter.sing();
}
}
Singer 를 상속 받았지만 SongWriter 를 아직 상속받지 못한 상태입니다.
2. 다중 상속으로 인한 예외 발생
SongWriter를 상속 받으려 했으나 추상 클레스 특성상 다중 상속이 불가 한 상황입니다.
3. 거듭 상속? 을 이용해 해결?
sing 이 songwriter 를 상속받고, SingerSongWriter 를 상속받는 구조라면 모두 나타낼 수 있지 않을까요?
abstract class Singer {
abstract void sing();
}
abstract class SongWriter extends Singer{ // SongWriter가 Singer를 상속받음.
abstract void songWriter();
}
class SingerSongWriter extends SongWriter { // SingerSongWriter가 Singer를 상속받은 SongWriter를 상속받음.
@Override
public void sing() {
System.out.println("노래 잘해용");
}
@Override
void songWriter() {
System.out.println("작곡 잘해용");
}
public static void main(String[] args) {
SingerSongWriter singerSongWriter = new SingerSongWriter();
singerSongWriter.sing(); // 노래 잘해용
singerSongWriter.songWriter(); // 작곡 잘해용
}
}
성공했습니다.
4. Sing 과 SongWriter 에 Dance 기능도 추가된다면?
추상클래스로 나타내려면, 아마도 Sing + Dance , SongWriter + Dance, Sing + SongWriter + Dance 등의 조합의 새로운 코드를 작성해야 할 것입니다.
그렇다면 인터페이스는 어떨까요?
인터페이스의 다중 상속
인터페이스는 여러 개를 동시에 상속받을 수 있습니다.
↓ 코드 예시 ↓
1. 인터페이스를 통해 다중 상속을 받습니다.
SingerSongWriter를 먼저 구현해 보았습니다.
interface Singer {
void sing();
}
interface SongWriter {
void songWriter();
}
class SingerSongWriter implements Singer, SongWriter{ // Singer, SongWriter를 동시에 상속받음.
@Override
public void sing() {
System.out.println("노래 잘해용");
}
@Override
public void songWriter() {
System.out.println("작곡 합니당");
}
public static void main(String[] args) {
SingerSongWriter singerSongWriter = new SingerSongWriter();
singerSongWriter.sing(); // 노래 잘해용
singerSongWriter.songWriter(); // 작곡 합니당
}
}
다중 상속이 가능해 다중 상속 상황에 인터페이스가 유리함을 알 수 있습니다.
2. Dance 기능 추가
interface Dancer {
void dence();
}
... 생략 ...
class SingerSongWriter implements Singer, SongWriter{
... 생략 ...
}
class SingerSongWriterAndDancer implements Singer, SongWriter, Dancer{
... 생략 ...
}
마찬가지로 손쉽게 Dance 기능을 추가 하는데 성공했습니다.
만약 다중 상속 시 서로 다른 인터페이스에 같은 이름의 메서드가 있다면 어떤 클래스의 매서드를 상속받을까요? 궁금하다면..
해당 문제는 ' 다이아 몬드 문제'라 부른다고 합니다.
다이아몬드 문제란 다중 상속에서 여러 클래스가 같은 클래스를 상속할 때 발생하는 모호성입니다.
자바에서는 다중 상속을 클래스 단위로 지원하지 않습니다.
하지만 인터페이스에서는 여러 개의 인터페이스를 구현할 수 있어 다이아몬드 문제가 발생할 수 있다고 봅니다.
쉽게 말해 다중 상속 시 같은 이름의 추상메서드가 있을 때 어떤 인터페이스의 메서드를 상속받아야 하는지 판별할 수 없는 문제입니다.
하지만 문제될것이 없습니다!
1. default 메서드와 static 메서드는 부모 클래스 명이 명시되어야 하기때문에 상관없으며 충돌시 오버라이드로 해결가능합니다.
2. abstractMethod 역시 이름이 같다 해도, 각 부모 클래스에 구체적인 선언부가 없어 자식 클래스에서 재 정의해 사용하기 때문에 자식 클래스가 오버라이드한 내용 대로 출력됩니다. 때문에 문제되지 않습니다.
↓ 코드 예시 ↓
메서드 이름이 같은 인터페이스 A와 ATemp 코드입니다.
이름이 같다해도, 자식 클래스에서 재정의가 이루어 지거나, 부모 클래스 이름이 명시되기 때문에 문제되지 않음을 확인할 수 있습니다.
interface A {
void abstractMethod();
default void defaultMethod(){
System.out.println("A default Method");
};
static void staticMethod(){
System.out.println("A static Method");
};
}
interface ATemp {
void abstractMethod();
default void defaultMethod(){
System.out.println("ATemp default Method");
};
static void staticMethod(){
System.out.println("ATemp static Method");
};
}
class B implements A, ATemp{
@Override
public void abstractMethod() {
System.out.println("B abstractMethod");
}
@Override
public void defaultMethod() {
A.super.defaultMethod();
ATemp.super.defaultMethod();
}
public static void main(String[] args) {
B b = new B();
b.abstractMethod(); // B abstractMethod
b.defaultMethod(); // A default Method, ATemp default Method
A.staticMethod(); // A static Method
}
}
인터페이스의 활용
인터페이스는 공통된 관계를 나타내기 위해 인터페이스를 사용하기도 합니다.
자동차의 경적 소리와 동물의 울음소리에 대해 ‘소리’라는 인터페이스를 만들어 관계를 나타내보았습니다.
↓ 코드 예시 ↓
interface Sound { // 관계를 나타내기 위한 인터페이스
void makeSound();
}
interface Animal{
String getName();
int getAge();
}
class Dog implements Animal, Sound{ // 인터페이스의 다중 상속
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void makeSound() {
System.out.println("왈왈");
}
@Override
public String getName() {
return name;
}
@Override
public int getAge() {
return age;
}
}
class Car implements Sound{
@Override
public void makeSound() {
System.out.println("빵빵");
}
}
class Main{
public static void main(String[] args) {
Sound dog = new Dog("강아지", 10);
Sound car = new Car();
dog.makeSound(); // 왈왈
car.makeSound(); // 빵빵
}
}
인터페이스와 추상 클레스를 같이 써보자
인터페이스와 추상 클래스는 다형성을 지원합니다.
다형성을 객체지향 프로그래밍(OOP)의 핵심 개념 중 하나입니다.
다형성은 객체 지향 프로그래밍에서 하나의 인터페이스나 추상 클래스를 상속받아 사용할 수 있음을 나타냅니다.
인터페이스와 추상 클래스를 함께 사용해 객체지향적 프로그래밍을 하고 코드의 재사용성을 높일 수 있습니다.
↓ 코드 예시 ↓
1. 인터페이스 + 추상 클래스
아래 예시 코드는 '학생' 인터페이스가 학생의 행동을 정의합니다.
'학생' 인터페이스를 추상 클래스인 '초등학교 학생', '고등학교 학생'이 상속받아 '학생'의 공통된 행동을 구현합니다.
이때 초등학생, 고등학생에 따라 구현부가 달라짐을 알 수 있습니다.
마지막으로 최종 구현체 클래스에서 알맞는 추상 클래스를 상속받아 메서드를 사용합니다.
interface Student { // 인터페이스
void goToSchool();
void learnMath();
void leaveSchool();
}
abstract class ElementarySchoolStudent implements Student{ // 인터페이스를 상속받은 추상클레스
@Override
public void goToSchool() {
System.out.println("초등학교로 등교합니다.");
}
@Override
public void learnMath() {
System.out.println("덧셈을 배웁니다.");
}
@Override
public void leaveSchool() {
System.out.println("2시에 하교합니다.");
}
}
abstract class highSchoolStudent implements Student{// 인터페이스를 상속받은 추상클레스
@Override
public void goToSchool() {
System.out.println("고등학교로 등교합니다.");
}
@Override
public void learnMath() {
System.out.println("미적분을 배웁니다.");
}
@Override
public void leaveSchool() {
System.out.println("6시에 하교합니다.");
}
}
class Suhyeon extends HighSchoolStudent{
public static void main(String[] args) {
Student suhyeon = new Suhyeon();
suhyeon.goToSchool();
}
}
2. default 메서드를 추가해 보았습니다.
인터페이스에 초등학생, 고등학생의 공통된 행동인 ‘급식을 먹는다.’ 와 ‘수업을 듣는다.’ 를 추가하였습니다.
interface Student {
void goToSchool();
void learnMath();
void leaveSchool();
// 추가
default void eatLunch(){
System.out.println("맛있는 점심을 먹습니다.");
};
default void takeClasses(){
System.out.println("지루한 수업을 들어요");
};
}
... 생략 ...
abstract class HighSchoolStudent implements Student{
@Override
public void goToSchool() {
System.out.println("고등학교로 등교합니다.");
}
@Override
public void learnMath() {
System.out.println("미적분을 배웁니다.");
}
@Override
public void leaveSchool() {
System.out.println("6시에 하교합니다.");
}
}
class Suhyeon extends HighSchoolStudent{
public static void main(String[] args) {
Student suhyeon = new Suhyeon();
suhyeon.goToSchool(); // 고등학교로 등교합니다.
suhyeon.takeClasses(); // 지루한 수업을 들어요
}
}
인터페이스의 default 메서드를 사용해 공통된 기능을 손쉽게 사용할 수 있도록하였습니다.
default 메서드의 취지는 이미 작성된 코드에서 공통된 기능을 추가하도록하기 위함입니다.
인터페이스 생성시 작성하는것을 지양하는것이 좋다고 생각합니다.
인터페이스는 주로 다중 상속과 관련된 기능을 제공하는 데 집중하도록 작성하는 것이 좋습니다.
💡 결론 💡
공통
추상 클래스와 인터페이스 모두 상속받은 자식 클래스에게 구현을 위임합니다.
차이
인터페이스
- 다중 상속이 가능합니다.
- 관련 없는 클래스들 끼리 관계를 맺을 수 있습니다.
- 특정 데이터 타입의 동작을 지정하지만 해당 동작을 누가 구현하는지 중요하지 않을 때 사용하기 좋습니다.
추상 클래스
- 다중 상속이 불가능 합니다.
- 인터페이스와 다르게 static 또는 final 을 사용할 수 없습니다.
참고한 자료
https://youtu.be/T1BJzC9xb0g?si=VUYbm45g5aqA3ofB
책 Java의 정석 - 남궁 성
'Java' 카테고리의 다른 글
[ Java ] Java, Python, C 특징 및 차이점 (2) | 2023.12.23 |
---|---|
[ Java ] Stream 의 reduce 사용해보자 (0) | 2023.12.20 |
[ Java ] 리스트 ↔ 배열 변환 (0) | 2023.04.04 |
[Java] Arrays.sort() 로 오름차순 , 내림차순 정렬하기 ( + Integer[] , int[]) (0) | 2023.02.16 |