[JAVA] 추상클래스와 인터페이스

Ming·2022년 9월 27일
0

자바

목록 보기
6/14

추상클래스

추상클래스(abstract class)

클래스가 설계도라면 추상클래스는 미완성 설계도로 미완성 메서드를 갖고 있는 클래스이다. 추상메서드는 몸통이 없는 대신 abstract를 붙여주어야한다. 추상클래스도 앞에 abstract를 붙여주어야한다.

abstract class Player{ //추상클래스(미완성 클래스)
	abstract void play(int pos); //추상메서드(몸통이 없는 미완성 메서드)
    abstract void stop(); //추상메서드
}
class AudioPlayer extends Player{
	void play(int pos) { ... } //추상메서드를 구현
    void stop() { ... }	//추상메서드를 구현
}
Player p = new Player(); //에러
AudioPlayer ap = new AudioPlayer();

추상클래스는 다른 클래스 작성에 도움을 주기 위한 용도이기 때문에 인스턴스 생성이 불가능하다. 대신 추상클래스를 상속받는 자손 클래스에서 추상 메서드의 구현부를 완성해서 사용할 수 있다. 그렇기 때문에 자손마다 구현부는 다르게 구현된다. 자손클래스는 추상메서드를 구현했기 때문에 abstract를 붙이지 않는다.

추상 메서드(abstract method)

구현부가 없는 메서드로 미완성 메서드이다. 꼭 필요하지만 추상클래스를 상속받는 자손클래스마다 다르게 구현될 것이라고 예상되는 경우에 사용한다.

abstract class Video extends Player{
	void play(int pos) { ... }
}

위의 예시와 같이 추상클래스의 추상메서드 중 일부만 구현한 경우는 자손 클래스도 미완성이기 때문에 클래스 선언 앞에 abstract를 붙여야한다.

abstract class Player {
	boolean pause;
    int currentPos;
    
    Player() {
    	pause = false;
        currentPos = 0;
    }
    
    abstract void play(int pos);
    abstract void stop();
    
    void play() {
    	play(currentPos); //추상메서드 사용 가능하다
    }
}

play(currentPos)를 보면 추상메서드를 호출하는 것을 확인할 수 있다. 메서드는 선언부만 알면 호출이 가능하기 때문에 추상메서드도 호출이 가능하다. 또한, 지금은 구현부가 없지만 상속을 통해 자손이 완성을 하고 객체 생성을 한 후 호출을 하기 때문에 추상메서드를 사용할 수 있다.

추상클래스의 작성

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

인터페이스(Interface)

인터페이스는 추상 메서드의 집합이다. 즉, 구현된 것이 전혀 없는 설계도이다. 또한, 인터페이스의 모든 멤버는 public이다. 인터페이스의 모든 메서드는 추상 메서드이고 public이기 때문에 메서드를 선언할 때 publicabstract를 생략할 수 있다. 인터페이스의 상수 또한 항상 public이고 상수이기 때문에 public, final, static을 생략하거나 일부만 써도 된다.

✅ 추상클래스 vs 인터페이스
추상클래스는 일반 클래스인데 추상 메서드를 갖고 있는 것이고 인터페이스는 추상메서드만 갖고 그외의 것들은 갖지 않는 것이다. 추상클래스는 생성자와 멤버변수를 가질 수 있지만 인터페이스는 가질 수 없다.

형식

interface 인터페이스이름 {
	public static final 타입 상수이름 =;
    타입 상수이름 =; //public, static, final 생략 가능
    public abstract 타입 메서드이름(매개변수목록);
    타입 메서드이름(매개변수목록); //public abstract 생략가능
}

인터페이스는 상수와 추상메서드만 갖는다.

인터페이스의 상속

인터페이스의 조상은 인터페이스만 가능(Object가 최고 조상이아님)하고 다중 상속이 가능하다.

  • 다중상속
interface Fightable extends Movable, Attackable { }

interface Movable{
	void move(int x, int y);
}

interface Attackable {
	void attack(Unit u);
}

원래는 같은 메서드를 두개의 부모가 갖고 있는데 내용이 다르면 어떤 걸 상속받아야할지 알 수 없어서 메서드 충돌이 발생하기 때문에 단일상속이 원칙이다. 하지만 인터페이스는 선언부가 동일하지만 구현부가 없기 때문에 선언부가 동일해도 충돌이 발생하지 않기 때문에 다중상속이 가능하다.

인터페이스의 구현

인터페이스는 미완성 설계도이기 때문에 인스턴스 생성이 불가능하다. 그렇기 때문에 상속을 통해 인터페이스에 정의된 추상메서드를 완성하는 것해야 한다. 이것을 인터페이스의 구현이라고 한다.

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

아래와 같이 추상메서드 일부만 구현하는 경우는 클래스 앞에 abstract를 붙여야한다.

interface Fightable{
	void move(int x, int y);
    void attack(Unit u);
}
abstract class Fighter implements Fightable{
	public void move(int x, int y) { ... }
}

인터페이스와 다형성

인터페이스 타입 변수로 인터페이스를 구현한 클래스의 인스턴스를 참조

class Fighter extends Unit implements Fightable{
	public void move(int x, int y) { ... }
    public void attack(Fightable f) { ... }
}
Unit u = new Fighter();
Fightable f = new Fighter();

자손 객체를 조상 클래스의 참조변수로 가리킬 수 있는 것처럼 인터페이스 타입의 참조변수로 자손 객체를 가리킬 수 있다. 대신 인터페이스 타입 참조변수는 인터페이스로 정의된 메서드만 사용할 수 있다. 즉, move와 attack만 사용가능하다.

인터페이스 타입의 매개변수

interface Fightable {
	void move(int x, int y);
    void attack(Fightable f);
}

인터페이스 타입 매개변수는 인터페이스 구현한 클래스의 객체만 가능하다.위의 예제에서 void attack(Fightable f)를 보면 매개변수의 타입이 매개변수이다. 이 의미는 attack 메서드는 Fightable 인터페이스를 구현한 클래스의 객체만 받겠다는 의미이다.

인터페이스 타입 반환

Fightable method(){
	...
    Fighter f = new Fighter();
    return f;
}

class Fighter extends Unit implements Fightable {
	public void move(int x, int y) { ... }
    public void attack(Fightable f) { ... }
}

위의 예제에서 보면 메서드의 반환 타입이 인터페이스이다. 이 경우는 Fightable 인터페이스를 구현한 클래스의 객체를 반환한다는 의미이다. 즉, Fighter 클래스가 Fightable 인터페이스를 구현했기 때문에 method()에서 Fighter를 반환할 수 있다.

인터페이스의 이해

  • 두 대상(객체)간의 연결, 대화, 소통을 돕는 중간 역할을 한다.
  • 선언(설계)와 구현을 분리시킬 수 있게 한다.
    class B {
    	public void method(){
      	System.out.println("methodInB");
      }
    }
    interface I {
    	public void method();
    }
    class B implements I{
    	public void method(){
      	System.out.println("methodInB");
      }
    }
    위와 같이 인터페이스를 이용하여 선언부분과 구현부분을 분리시킬 수 있다. 인터페이스를 사용하기 전에는 유연하지 않고 변경에 불리하다. 하지만 인터페이스를 사용하면 껍데기와 알맹이를 분리했기 때문에 알맹이 변경에 유리하고 유연하다.
  • 인터페이스 덕분에 B가 변경되어도 A는 안바꿀 수 있게 된다.(느슨한 결합)
    A가 B를 사용할 때 B를 C로 변경하면 A의 변경이 불가피하다.(강한 결합) 하지만 인터페이스를 이용하면 A클래스는 인터페이스와만 관계가 있고 B클래스와는 직접적으로 관련이 없기 때문에 B가 C로 변경되어도 A 클래스는 변경 사실을 모르기 때문에 수정을 하지 않아도 된다.(느슨한 결합)
    • 강한 결합(A-B)
      class A {
      	public void methodA(B b) {
        	b.methodB();
        }
      }
      class B {
      	public void methodB() {
        	System.out.println("methodB()");
        }
      }
      class InterfaceTest {
      	public static void main(String args[]) {
        	A a = new A();
            a.methodA(new B());
        }
      }
      	```
        
    • 느슨한 결합 (A-I-B)
      interface I { void methodB(); }
      class B implements I {
      	public void methodB() {
        	System.out.println("methodB()");
        }
      }
      class A {
      	public void methodA(I i) {
        	i.methodB();
        }
      }

인터페이스의 장점

  • 개발 시간을 단축할 수 있다.
    강한 결합인 경우는 B가 완성되어있어야 A가 B를 이용할 수 있다. 반면, 느슨한 결합인 경우 A는 B가 완성되어있지 않아도 I만 있어도 코드를 작성할 수 있다.
  • 변경에 유리한 유연한 설계가 가능하다.
  • 표준화가 가능하다.
  • 독립적인 프로그래밍이 가능하다.
  • 서로 관계없는 클래스들을 관계를 맺어줄 수 있다.

디폴트 메서드와 static 메서드

인터페이스에 새로운 메서드를 추가하려 할 때 추상 메서드이기 때문에 인터페이스를 사용하는 모든 클래스에서 메서드를 추가해야한다. 이를 해결하기 위한 방법이 디폴트메서드이다.

interface MyInterfacd {
	void method();
    void newMethod(); //추상 메서드
}
interface MyInterface {
	void method();
    default void mewMethod() { }
}

default키워드를 앞에 붙여주면 된다. 디폴트 메서드는 인스턴스 메서드이기 때문에 인터페이스 원칙에 위반한 것이다.

디폴트 메서드가 기존의 메서드와 충돌할 때의 해결책

  • 여러 인터페이스의 디폴트 메서드 간의 충돌
    인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
  • 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
    조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.

[출처]
자바의 정석 - https://www.youtube.com/playlist?list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp

0개의 댓글

관련 채용 정보