반응형
# 목적
: 추상화 학습을 위한 추상클래스, 인터페이스의 개념 및 차이점 정리
추상화란?
: 구현클래스가 반드시 포함할 기능(구현할 기능)을 추상메소드로 정의하는 것
* 추상화의 목적 - 사용방법이 동일한 구현클래스의 작성
* 상속의 목적 - 자식클래스를 빠르게 구현하기 위함
❓ 추상 클래스(abstract Class)란?
: 미완성 메서드를 갖고 있는 미완성 설계도(클래스)
- 다른 클래스 작성에 도움을 주기 위한 것으로, 인스턴스 생성 불가
(미완성 설계도 -> 제품 생산 불가)
- 상속을 통해 추상 메서드를 완성해야 인스턴스 생성 가능
추상클래스 작성 기준
: 여러 클래스에 공통적으로 사용될 수 있는 추상클래스를 바로 작성하거나
기존 클래스의 공통 부분을 뽑아서 추상클래스를 만든다.
추상클래스의 장점
- 부모의 설계도를 상속받아 자손 설계도의 중복을 제거하고 쉽게 작성 가능
- 코드의 관리가 용이
ex) 부모의 특정 메서드 1개를 변경하면 물려받은 자손들도 해당 메서드가 자동으로 변경됨- 상속을 통해 단계별로 추상클래스를 생성하여 구체적으로 설계 가능
(조상의 자손의 자손의 자손 ...)
> 추상화된 코드는 구체화된 코드보다 유연 = 변경에 유리
❓ 인터페이스(interface)란?
: 추상 메서드의 집합으로, 구현된 것이 전혀 없는 설계도 (껍데기)( 프로그래밍 관점에서의 정의 )
인터페이스의 상속
- 인터페이스의 조상은 인터페이스만 가능
* Class의 최고 조상 : Object / Interface의 최고 조상 : Interface
- 다중 상속 가능 -> 1개의 인터페이스에 조상이 여러개
(추상메서드는 충돌해도 문제 없음)
인터페이스 구현 방법
형식 :
class 클래스이름 implements 인터페이스이름 { // 인터페이스에 정의된 추상메서드를 모두 구현해야 함 }
*인터페이스 구현 : 인터페이스에 정의된 추상메서드를 완성하는 것
인터페이스를 이용한 다형성
- 인터페이스도 구현 클래스의 부모라고 할 수 있음
- 인터페이스 타입 매개변수는 인터페이스 구현한 클래스의 객체(인스턴스)만 가능
- 인터페이스를 메서드의 리턴타입으로 지정할 수 있음
예시)// 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 객체를 반환 } }
인터페이스의 장점
- 개발시간 단축
- 변경에 유리, 유연한 코드 설계 가능
- 표준화 가능 ex)JDBC를 이용해서 DB를 구현할 때
- 서로 관계없는 클래스들을 관계 맺어줄 수 있음
> 두 대상(객체) 간의 '연결, 대화, 소통'을 돕는 '중간 역할'
> 선언(설계, 껍데기)와 구현(알맹이)을 분리 가능
> 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
'BackEnd > Java' 카테고리의 다른 글
[Java] 컬렉션 프레임워크(Collection Framework) 개념 (0) | 2025.01.15 |
---|---|
[Java] StringBuilder, StringBuffer 사용 목적 및 차이점 (0) | 2025.01.15 |
[Java] 다형성(Polymorphism) 정리 (0) | 2025.01.15 |
[Java] 접근 제어자(access modifier) (Feat. 캡슐화) (0) | 2025.01.15 |
[Java] 오버로딩(Overloading), 생성자(Constructor) (0) | 2025.01.15 |