추상 클래스
미완성 설계도. 미완성 메서드를 갖고 있는 클래스
abstract class Player { // 추상클래스(미완성 클래스)
abstract void play(int pos); // 추상메서드(몸통{}이 없는 미완성 메서드)
abstract void stop(); // 추상 메서드
}
다른 클래스 작성에 도움을 주기 위한 것(인스턴스 생성 불가)
Player p = new Player(); X
상속을 통해 추상 메서드를 완성해야 인스턴스 생성가능
구현: 추상 메서드 구현 해주기
AudioPlayer ap = new AudioPlayer(); // Ok (AudioPlay는 Player를 구현한 구현 클래스)
Play p = new AudioPlayer(); // OK
- 실제 p에 쓸 수있는 것은 play(), stop() 이지만 구현 클래스의 오버라이딩으로 인해 자식의 메서드가 호출된다.
- 다운 캐스팅을 통해 AudioPlayer 만이 쓸 수 있는 메서드도 호출할 수 있을 것 같다.
추상 메서드
미완성 메서드, 구현부가 없는 메서드
일부분만 구현하는 경우에는 abstract 키워드를 꼭 붙인다!
class AbstractPlayer extends Player {
void play(int pos){}
void stop(){}
}
abstract class AbstractPlayer extends Player { // 구현을 완성하지 않았다면 abstract
void play(int pos){}
// void stop() 구현하지 않았음
}
어떨 때 쓸까?
꼭 필요하지만 자손마다 다르게 구현될 것으로 예상되는 경우
필수기능을 강제시킬 때
예시
abstract class Player { // 추상클래스(미완성 클래스)
boolean pause; // 일시정지 상태를 저장하기 위한 변수
int currentPos; // 현재 Play 되고 있는 위치를 저장하기 위한 변수
Player() {
pause = false;
currentPos = 0;
}
abstract void play(int pos); // 추상메서드(몸통{}이 없는 미완성 메서드)
abstract void stop(); // 추상 메서드
void play() { // 인스턴스 메서드(추상 메서드를 사용할 수 있다.) <- 상속을 통해 자손이 완성하면 호출OK
play(currentPos);
}
}
abstract class Player { // 추상클래스(미완성 클래스)
abstract void play(int pos); // 추상메서드(몸통{}이 없는 미완성 메서드)
abstract void stop(); // 추상 메서드
}
// 추상 클래스는 상속을 통해 완성해야 객체 생성가능
class AudioPlayer extends Player {
void play(int pos) {
System.out.println(pos + "위치 부터 play 합니다");
}
void stop() {
System.out.println("재생을 멈춥니다.");
}
}
public class PlayerTest {
public static void main(String[] args) {
// Player p = new Player(); 추상 클래스의 객체를 생성
//AudioPlayer ap = new AudioPlayer();
Player ap = new AudioPlayer(); // 다형성
ap.play(100);
ap.stop();
}
}
추상화를 사용하지 않았을 때의 문제점
- Player 클래스를 생성할 수 있는 문제
- Player 클래스를 상속 받는 곳에서 play( ) 메서드 오버라이딩을 하지 않을 가능성(필수 기능)
추상 클래스의 작성
여러 클래스에 공통적으로 사용될 수 있는 추상 클래스를 바로 작성하거나
기존 클래스의 공통 부분을 뽑아서 추상클래스를 만든다.
abstract class Amy {
int x, y;
abstract void move(int x, int y);
abstract void stop();
}
public class Marine extends Amy{
void move(int x, int y) {
System.out.println("x 좌표: " + x + ", y좌표: " + y + " 지상으로 이동한다.");
}
void stop() {
System.out.println("보병을 멈춘다.");
}
void stimPack() {
System.out.println("스팀팩을 사용한다.");
}
}
// 수송선
public class Dropship extends Amy{
void move(int x, int y) {
System.out.println("x 좌표: " + x + ", y좌표: " + y + " 공중으로 이동한다.");
}
void stop() {
System.out.println("수송선을 멈춘다.");
}
void load() {
System.out.println("선택된 대상을 태운다.");
}
void unload() {
System.out.println("선택된 대상을 내린다.");
}
}
// 탱크
public class Tank extends Amy{
void move(int x, int y) {
System.out.println("x 좌표: " + x + ", y좌표: " + y + " 지상으로 이동한다.");
}
void stop() {
System.out.println("탱크을 멈춘다.");
}
void changeMode() {
System.out.println("공격모드로 전환!");
}
}
public class AmyMain{
public static void main(String[] args) {
Amy[] amies = {new Dropship(), new Tank(), new Marine()};
//Object[] amies = {new Dropship(), new Tank(), new Marine()}; 리모콘 타입이 obj<-에는 move가 없음
for (Amy amy : amies) {
amy.x = 1;
amy.y = 3;
if (amy instanceof Tank t) {
amy.x = 100;
amy.y = 200;
t.changeMode();
}
amy.move(amy.x, amy.y);
amy.stop();
}
}
}
x 좌표: 1, y좌표: 3 공중으로 이동한다.
수송선을 멈춘다.
공격모드로 전환!
x 좌표: 100, y좌표: 200 지상으로 이동한다.
탱크을 멈춘다.
x 좌표: 1, y좌표: 3 지상으로 이동한다.
보병을 멈춘다.
추상화의 장점
- 설계도를 쉽게 작성
- 중복 제거
- 관리가 용이
추상 클래스를 단계별로 만든다면 ?
상속받으면서 클래스가 점점 구체적이게 된다.
의미있는 단계별로 작성한다면 중간 단계를 선택해서 구현할 수 있다.
추상적 <========> 구체적
(불명확) (명확)
일반적으로 구체적인게 좋지만 불명확할 때가 필요할 때도 있다.
(여자친구에게 술 마시러 간다 => 여자친구에게 친구랑 만난다 한다)
추상화된 코드는 구체화된 코드보다 유연하다. (말바꾸기 쉽다.)
변경에 유리!!
인터페이스
결론: 추상메서드의 집합
핵심: 구현된 것이 전혀 없는 설계도. 껍데기(모든 멤버가 public: 껍데기 여서)
인터페이스 추상클래스 차이점
- 추상 클래스: 일반 클래스(생성자, 인스턴스 변수) + 추상메서드를 가지고 있는 것(일부 미완성)
- 인터페이스: 추상 메서드만 가지고 있음(완전 미완성)
interface 인터페이스 이름 {
public static final 타입 상수이름 = 값;
public abstract 메서드이름(매개변수 목록); // 모든 접근제어자: public
}
interface PlayingCard {
public static final int SPADE = 4;
final int DIAMOND = 3; // public static final int DIAMOND;
static int HEART = 2; // public static final int HEART;
int CLOVER = 1; // public static final int CLOVER; 항상 public, static, final
public abstract String getCardNumber();
String getCardKind(); // public abstract 생략가능
}
- 인터페이스의 조상은 인터페이스만 가능(Object가 최고 조상 아님!!)
- 다중 상속이 가능!!!(추상 메서드는 충돌해도 문제 없음)
- 구현부(몸통)이 없으니까 상관없음!
interface Fightable extends Movable, Attackable{ }// 멤버 2개
interface Movable {
void move(int x, int y};
}
interface Attackable{
void attack(Unit u);
}
인터페이스의 구현
구현: 인터페이스에 정의된 추상 메서드를 완성하는 것(추상 클래스처럼 몸통 만들기)
class 클래스이름 implements 인터페이스 이름 {
// 인터페이스에 정의된 추상메서드를 모두 구현해야 한다.
}
"Fighter 클래스는 Fightable 인터페이스를 구현했다." (몸통을 만들어줬다!!)
class Fighter implements Fightable {
public void move(int x, int y) {// 구현 }
public void attack(Unit u) {// 구현 }
}
일부만 구현하는 경우, 클래스 앞에 abstracet 붙여야 함!!!
추상클래스의 구현
구현: 상속(extends)을 통해서 구현
인터페이스의 구현
구현: 구현(implements)을 통해서 구현
둘다 미완성 설계도를 완성시킨다는 점에서는 공통점이 있다.
Q. 인터페이스란?
A. 추상 메서드의 집합(Java 1.8부터 상수, static 메서드, default 메서드 추가 => 핵심은 아님)
Q. 인터페이스의 구현이란?
A. 인터페이스의 추상메서드 몸통{ } 만들기(완전 미완성 설계도 완성하기)
Q. 추상 클래스와 인터페이스의 공통점은?
A. 추상 메서드를 가지고 있다.(미완성 설계도)
Q. 추상 클래스와 인터페이스의 차이점은?
A. 인터페이스는 인스턴스 변수, 생성자, 인스턴스 메서드 를 가질 수 없다.
인터페이스를 이용한 다형성
1. 인터페이스도 구현 클래스의 부모일까? => Yes(정확히 말하면 부모는 아니지만 거의 부모다. 반부모?)
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { // 구현 }
public void attack(Unit u) { // 구현 }
}
다중 상속 문제
- 그렇다면 Fighter 클래스는 뭘 상속해야 할까? (충돌)
- 인터페이스는 몸통이 없으므로 선언부가 충돌해도 상관없다.
Unit u = new Fighter();
Fightable f = new Fighter(); // 둘다 쌉가능,
//대신 추상클래스와 마찬가지로 인터페이스의 선언한 멤버2개만 사용가능
f.move(100, 200)
f.attack(new Fighter());
2. 인터페이스 타입 매개변수는 인터페이스를 구현한 클래스의 객체만 가능
interface Fightable {
void move(int x, int y);
void attack(Fightable f);
}
3. 인터페이스를 메서드의 리턴타입으로 지정할 수 있다.
Fightable 인터페이스를 구현한 클래스의 인스턴스를 반환한다.
Fightable method() { // 리턴타입 Fightable
...
Fighter f = new Fighter();
return f;
// 위의 두 문장을 한 문장으로 바꾸면
return new Fighter();
}
Fightable f = method();
Fightable f = new Fighter();
- 메서드의 반환타입이 인터페이스라면 메서드 안에서는 인터페이스를 구현한 객체를 반환해야 한다.
- 그리고 메서드를 호출한 쪽에서는 반환타입이 일치하는, 또는 자동형변환이 가능한 타입의 변수에 결과를 저장해야 한다.
예시
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { // 내용 생략 }
public void attack(Fightable f) { // 내용 생략 }
}
Fightable method() { // 리턴타입 Fightable
...
Fighter f = new Fighter();
return f;
// 위의 두 문장을 한 문장으로 바꾸면
// return new Fighter();
}
// 아래 문장은 서로 같다.
Fightable f = method();
Fightable f = new Fighter();
정리
abstract class Unit {
int x, y;
abstract void move(int x, int y);
void stop() {
System.out.println("멈춥니다.");
}
}
interface Fightable {
void move(int x, int y); // public abstract 생략
void attack(Fightable f); // 인터페이스 타입 매개변수는 인터페이스를 구현한 클래스의 객체만 가능
}
class Fighter extends Unit implements Fightable { // 인터페이스의 모든 메서드는 public abstract. 예외없이
// 오버라이딩 규칙: 조상(public)보다 접근제어자가 범위가 좁으면 안된다.
public void move(int x, int y) {
System.out.println("[" + x + ", " + y + "]으로 이동" );
public void attack(Fightable f) { // 인터페이스 타입 매개변수는 인터페이스를 구현한 클래스만 가능
System.out.println(f + "를 공격");
}
}
public class FighterTest {
public static void main(String[] args) {
Fighter f = new Fighter();
f.move(100, 200);
f.attack(f); // 다형적 참조 가능
f.attack(new Fighter());// 인터페이스 타입 매개변수는 인터페이스를 구현한 클래스의 객체만 가능
f.stop();
Fightable f2 = new Fighter();
f2.move(150, 300);
f2.attack(f2); // 다형적 참조 가능
f2.attack(new Fighter());
//f2.stop(); Fightable에는 stop()이 없어서 호출불가
Unit u = new Fighter();
u.move(1, 3);
u.stop();
//u.attack(new Fighter()); Unit에는 attack() 이 없어서 호출불가
}
}
package ex.oop.clazz;
abstract class Unit {
int x, y;
abstract void move(int x, int y);
void stop() {
System.out.println("멈춥니다.");
}
}
interface Fightable {
void move(int x, int y); // public abstract 생략
void attack(Fightable f); // 자기 자신을 타입으로 둘 수 있음(다형성) or 인터페이스를 구현한 객체
}
class Fighter extends Unit implements Fightable { // 인터페이스의 모든 메서드는 public abstract. 예외없이
// 오버라이딩 규칙: 조상(public)보다 접근제어자가 범위가 좁으면 안된다.
public void move(int x, int y) {
System.out.println("[" + x + ", " + y + "]으로 이동" );
}
public void attack(Fightable f) { // 인터페이스 타입 매개변수는 인터페이스를 구현한 클래스만 가능
System.out.println(f + "를 공격");
}
// 싸울 수 있는 상대를 불러온다.
Fightable getFightable() {
// Fighter f = new Fighter(); // Fighter를 생성해서 반환
// Fightable f = new Fighter();
return new Fighter(); // 자동 형변환
//인터페이스를 메서드의 리턴타입으로 지정할 수 있다.
}
}
public class FighterTest {
public static void main(String[] args) {
Fighter f = new Fighter();
f.move(100, 200);
f.attack(f); // 다형적 참조 가능
f.attack(new Fighter());
f.stop();
Fightable f3 = f.getFightable();
/* 그리고 메서드 호출한 쪽에서는 반환타입이 일치하는,
또는 자동형변환이 가능한 타입의 변수에 결과를 저장해야 한다.*/
Fightable f2 = new Fighter();
f2.move(150, 300);
f2.attack(f2); // 다형적 참조 가능
f2.attack(new Fighter());
//f2.stop(); Fightable에는 stop()이 없어서 호출불가
Unit u = new Fighter();
u.move(1, 3);
u.stop();
//u.attack(new Fighter()); Unit에는 attack() 이 없어서 호출불가
}
}
'Java' 카테고리의 다른 글
[Java] 컬렉션 프레임워크, ArrayList (0) | 2024.05.05 |
---|---|
[Java] Arrays로 배열 다루기 (0) | 2024.05.02 |
[Java] 예외 처리 활용 (0) | 2024.04.23 |
[Java] 체크 예외 VS 언체크 예외 (0) | 2024.04.22 |
[Java] 중첩 클래스, 내부 클래스 (1) | 2024.04.18 |