본문 바로가기

Java

[Java 복습] 추상 클래스, 인터페이스

추상 클래스

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

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