클래스가 설계도라면 추상클래스는 미완성 설계도로 미완성 메서드를 갖고 있는 클래스이다. 추상메서드는 몸통이 없는 대신 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 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)를 보면 추상메서드를 호출하는 것을 확인할 수 있다. 메서드는 선언부만 알면 호출이 가능하기 때문에 추상메서드도 호출이 가능하다. 또한, 지금은 구현부가 없지만 상속을 통해 자손이 완성을 하고 객체 생성을 한 후 호출을 하기 때문에 추상메서드를 사용할 수 있다.
여러 클래스에 공통적으로 사용될 수 있는 추상클래스를 바로 작성하거나 기존클래스의 공통 부분을 뽑아서 추상클래스를 만든다.
인터페이스는 추상 메서드의 집합이다. 즉, 구현된 것이 전혀 없는 설계도이다. 또한, 인터페이스의 모든 멤버는 public이다. 인터페이스의 모든 메서드는 추상 메서드이고 public이기 때문에 메서드를 선언할 때 public
과 abstract
를 생략할 수 있다. 인터페이스의 상수 또한 항상 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");
}
}
위와 같이 인터페이스를 이용하여 선언부분과 구현부분을 분리시킬 수 있다. 인터페이스를 사용하기 전에는 유연하지 않고 변경에 불리하다. 하지만 인터페이스를 사용하면 껍데기와 알맹이를 분리했기 때문에 알맹이 변경에 유리하고 유연하다.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());
}
}
```
interface I { void methodB(); }
class B implements I {
public void methodB() {
System.out.println("methodB()");
}
}
class A {
public void methodA(I i) {
i.methodB();
}
}
인터페이스에 새로운 메서드를 추가하려 할 때 추상 메서드이기 때문에 인터페이스를 사용하는 모든 클래스에서 메서드를 추가해야한다. 이를 해결하기 위한 방법이 디폴트메서드이다.
interface MyInterfacd {
void method();
void newMethod(); //추상 메서드
}
interface MyInterface {
void method();
default void mewMethod() { }
}
default
키워드를 앞에 붙여주면 된다. 디폴트 메서드는 인스턴스 메서드이기 때문에 인터페이스 원칙에 위반한 것이다.
[출처]
자바의 정석 - https://www.youtube.com/playlist?list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp