본문 바로가기
BackEnd/Java

[Java] 추상화 - 추상 클래스(Abstract Class), 인터페이스(Interface) 정리

by ryuneng 2025. 1. 15.
반응형

# 목적

: 추상화 학습을 위한 추상클래스, 인터페이스의 개념 및 차이점 정리




추상화란?

: 구현클래스가 반드시 포함할 기능(구현할 기능)을 추상메소드로 정의하는 것

* 추상화의 목적 - 사용방법이 동일한 구현클래스의 작성
* 상속의 목적  - 자식클래스를 빠르게 구현하기 위함





❓ 추상 클래스(abstract Class)란?

: 미완성 메서드를 갖고 있는 미완성 설계도(클래스)

  1. 다른 클래스 작성에 도움을 주기 위한 것으로, 인스턴스 생성 불가
    (미완성 설계도 -> 제품 생산 불가)

  2. 상속을 통해 추상 메서드를 완성해야 인스턴스 생성 가능

추상클래스 작성 기준

: 여러 클래스에 공통적으로 사용될 수 있는 추상클래스를 바로 작성하거나
기존 클래스의 공통 부분을 뽑아서 추상클래스를 만든다.


추상클래스의 장점

  1. 부모의 설계도를 상속받아 자손 설계도의 중복을 제거하고 쉽게 작성 가능

  2. 코드의 관리가 용이
    ex) 부모의 특정 메서드 1개를 변경하면 물려받은 자손들도 해당 메서드가 자동으로 변경됨

  3. 상속을 통해 단계별로 추상클래스를 생성하여 구체적으로 설계 가능
    (조상의 자손의 자손의 자손 ...)

> 추상화된 코드는 구체화된 코드보다 유연 = 변경에 유리





❓ 인터페이스(interface)란?

: 추상 메서드의 집합으로, 구현된 것이 전혀 없는 설계도 (껍데기)
( 프로그래밍 관점에서의 정의 )


인터페이스의 상속

  1. 인터페이스의 조상은 인터페이스만 가능
    * Class의 최고 조상 : Object / Interface의 최고 조상 : Interface

  2. 다중 상속 가능 -> 1개의 인터페이스에 조상이 여러개
    (추상메서드는 충돌해도 문제 없음)

인터페이스 구현 방법

형식 :

class 클래스이름 implements 인터페이스이름 {
	// 인터페이스에 정의된 추상메서드를 모두 구현해야 함
}

*인터페이스 구현 : 인터페이스에 정의된 추상메서드를 완성하는 것


인터페이스를 이용한 다형성

  1. 인터페이스도 구현 클래스의 부모라고 할 수 있음

  2. 인터페이스 타입 매개변수는 인터페이스 구현한 클래스의 객체(인스턴스)만 가능

  3. 인터페이스를 메서드의 리턴타입으로 지정할 수 있음
    예시)
// 1. 인터페이스
interface Fightable {
	void move(int x, int y);
	void attack(Fightable f);
}
// 2. Fightable 인터페이스를 구현한 Fighter 클래스
class Fighter implements Fightable {
	public void move(int x, int y)  { /* 구현 내용 생략 */}
    public void attack(Fightable f) { /* 구현 내용 생략 */}
}
// 3. 리턴타입이 인터페이스인 메서드를 가지고 있는 클래스
class Test {
	Fightable method() { // 반환타입 : Fightable 인터페이스
		return new Fighter();
        // Fightable 인터페이스를 구현한 Fighter 객체를 반환
	}
}

인터페이스의 장점

  1. 개발시간 단축

  2. 변경에 유리, 유연한 코드 설계 가능

  3. 표준화 가능 ex)JDBC를 이용해서 DB를 구현할 때

  4. 서로 관계없는 클래스들을 관계 맺어줄 수 있음

> 두 대상(객체) 간의 '연결, 대화, 소통'을 돕는 '중간 역할'

> 선언(설계, 껍데기)와 구현(알맹이)을 분리 가능

> A class가 B class를 사용한다고 가정했을 때,
인터페이스 덕분에 B가 변경되어도 A는 바꾸지 않아도 된다. -> 느슨한 결합





📌 추상클래스와 인터페이스의 차이

  추상클래스 인터페이스
의미 추상 메서드가 있는 클래스 추상 메서드의 집합
생성자, iv 가질 수 있음 가질 수 없음





1-1. 추상클래스 기본 예시

abstract class Player {			 // 추상클래스(미완성 클래스)
	abstract void play(int pos); // 추상메서드
	abstract void stop();		 // 추상메서드
    // * 추상메서드 = 선언부만 있고 구현부(몸통{})이 없는 미완성 메서드
}

// 추상 클래스는 상속을 통해 완성해야 객체 생성 가능
class AudioPlayer extends Player {		   // 완전한 클래스
	void play(int pos) { /* 내용 생략 */ }  // 추상메서드를 구현
	void stop() { /* 내용 생략 */ }		   // 추상메서드를 구현
    // * 추상메서드를 구현 = 추상메서드로 몸통{} 만들기
}

abstract class AbstractPlayer extends Player { // 추상클래스
	void play(int pos) { /* 내용 생략 */ } 	   // 추상메서드 구현
    // 추상클래스인 이유 : 추상메서드를 구현했지만 1개만 구현했기 때문에 미완성
	// (상속받은 나머지 abstract void stop() 는 보이지 않더라도 존재하고 있음)
}


public class Test {
//	Player p = new Player(); // Error. 추상 클래스는 인스턴스 생성 불가
	AudioPlayer ap = new AudioPlayer(); // 완전한 클래스는 객체생성 가능
    
	Player p = new AudioPlayer(); // 가능 -> 다형성(실제 객체는 자손인 AudioPlayer)
    p.play(100); // AudioPlayer의 구현된 내용이 호출됨
    p.stop();    // AudioPlayer의 구현된 내용이 호출됨
    
  // 결론
  // 1. 추상 클래스 자체 : 객체생성 및 호출 불가
  // 2. 추상 클래스의 자손 : 객체 생성 및 호출 가능
  // 3. 추상 클래스를 다형성으로 객체 생성 : 생성 및 호출 가능
}


1-2. 추상클래스 적용 예시

// 1. 여러 병사의 조상이 될 Unit 추상클래스
abstract class Unit {
	int x, y; // 현재 위치
	abstract void move(int x, int y); // 지정된 위치로 이동
	void stop() { /* 현재 위치에 정지 */ }
}

// 2. Unit을 상속받은 Marine(보병) 클래스
class Marine extends Unit {
	void move(int x, int y) { // 지정된 위치로 이동
		System.out.println("Marine[x=" + x + ",y=" + y + "]");
	}
	void stimPack() 		{ /* 스팀팩 사용 */ }
}

// 3. Unit을 상속받은 Tank(탱크) 클래스
class Tank extends Unit {
	void move(int x, int y) { // 지정된 위치로 이동
		System.out.println("Tank[x=" + x + ",y=" + y + "]");
	}
	void changeMode() 		{ /* 공격모드 변환 */ }
}

// 4. Unit을 상속받은 Dropship(수송선) 클래스
class Dropship extends Unit {
	void move(int x, int y) { // 지정된 위치로 이동
		System.out.println("Dropship[x=" + x + ",y=" + y + "]");
	}
	void load() 			{ /* 선택된 대상을 태운다 */ }
	void unload() 			{ /* 선택된 대상을 내린다 */ }
}

// 5. main 메서드를 실행할 클래스
public class Test {
	public static void main(String[] args) {
		// Unit 객체배열로 Marine, Tank, Dropship 객체를 한번에 생성
		// * 객체배열 : 참조변수 묶은 것
		Unit[] group = { new Marine(), new Tank(), new Dropship() };
		
		// for문장을 통해 Marine, Tank, Dropship 객체의 move(100,200)을 한번에 호출
		// * group[i]의 타입은 Unit[], group[0,1,2]의 타입은 Unit
		for (int i=0; i < group.length; i++) {
			group[i].move(100, 200);
		}
	}
}





2-1. 인터페이스 기본 예시

// 1. 인터페이스에 있는 변수처럼 생긴 것들은 모두 '상수'
//  - 그 앞에는 항상 public static final이 자동으로 붙어있음 (전부 생략 가능)
// 2. 모든 인터페이스의 메서드는 기본적으로 항상 public abstract
//  - public abstract 생략 가능 (생략하더라도 있는 걸로 간주)

interface PlayingCard {
	public static final int SPADE = 4;
	final int DIAMOND = 3; 			// public static final int DIAMOND = 3;
	static int HEART =2;   			// public static final int HEART =2;
	int CLOVER = 1;		   			// public static final int CLOVER = 1;
	
	public abstract String getCardNumber();
	String getCardKind();			// public abstract String getCardKind();
}


2-2. 인터페이스 적용 예시

// 1. B클래스, C클래스의 선언과 구현을 분리할 인터페이스 I
interface I {
	public void method();
}

// 2. 인터페이스 I를 상속받은 B 클래스
class B implements I {
	public void method() {
		System.out.println("B클래스의 메서드");
	}
}

// 3. 인터페이스 I를 상속받은 C 클래스
class C implements I {
	public void method() {
		System.out.println("C클래스의 메서드");
	}
}

// 4. 메인메서드에서 B 클래스, C 클래스를 사용할 A 클래스
class A {
	// 1. 인터페이스를 사용하지 않았을 때
    // : 메인메서드에서 이 메서드를 호출할 때 C로 변경하고 싶으면
	public void method(B b) { // -> 이 A class의 메서드도 변경해주고,
		b.method();			  //  메인메서드에서도 C로 변경해줘야 함
	}
	
	// 2. 인터페이스를 사용했을 때
    // : 메인메서드에서 이 메서드를 호출할 때 B로 호출하든 C로 호출하든
    //   이 A class에서는 변경 안해줘도 됨 
	public void method(I i) { // (여기엔 인터페이스 I를 구현한
		i.method();			  //  class들만 들어올 수 있음)
	}
}

// 5. 메인메서드를 실행할 클래스
public class Test {
	public static void main(String[] args) {
		A a = new A();
		a.method(new B());	// A가 B를 사용(A가 B에 의존)
		a.method(new C());
	}
}




3. 추상클래스 & 인터페이스 적용 예시

// 1. 추상클래스 Unit
abstract class Unit {
	int x, y;
	abstract void move(int x, int y);
	void stop() { System.out.println("멈춥니다"); }
}

// 2. 인터페이스 Fightable
interface Fightable { // 인터페이스의 모든 메서드는 항상 public abstract
	void move(int x, int y);  // public abstract 생략됨
	void attack(Fightable f); // public abstract 생략됨
}

// 3. 추상클래스 Unit과 인터페이스 Fightable을 상속받은 클래스 Fighter
class Fighter extends Unit implements Fightable {
	// * 오버라이딩 규칙 : 조상보다 접근제어자의 범위가 좁으면 안됨
    //   -> public을 붙여야 함. 안붙이면 default로 좁아짐.
	public void move(int x, int y) {
		System.out.println("[" + x  + "," + y + "]로 이동");
	}
    
	public void attack(Fightable f) 	   {
		System.out.println(f + "를 공격");
	}
	
    // 싸울 수 있는 상대를 불러오는 메서드
	Fightable getFightable() {     // 반환타입 : 인터페이스 Fightable
		Fighter f = new Fighter(); // 클래스 Fighter를 생성해서 반환
		return f;
	}
}

// 4. 메인 메서드를 실행하는 클래스
public class Test {
	public static void main(String[] args) {
		Fighter f1 = new Fighter();
		Fightable f2 = f1.getFightable();
		
		Unit u = new Fighter();      // 추상클래스 다형성
		Fightable f = new Fighter(); // 인터페이스 다형성
		
		u.move(100, 200);
//		u.attack(new Fighter()); // Unit에는 attack()이 없어서 호출 불가
		u.stop();
		
		f.move(100, 200);
		f.attack(new Fighter());
//		f.stop();				 // Fightable에는 stop()이 없어서 호출 불가
	}
}

* 참고 : 유튜브 [자바의 정석 기초]


 


< 해당 글은 velog에서 이전하며 옮겨온 글로, 가독성이 좋지 않을 수 있는 점 양해 부탁드립니다. >

🔗 velog 버전 보기 : https://velog.io/@ryuneng2/Java-추상화-추상클래스-Abstract-인터페이스-Interface