처음에 객체지향프로그래밍을 공부할 때 어려웠던 것 중 하나가 추상클래스였다. 보통 추상 하면 서양미술에서의 추상화가 생각나고 뭔가 쉽게 알아보기 힘든 이미지였기 때문이다.
하지만 프로그래밍을 공부하며, 프로그래밍에서의 추상화는 복잡한 것을 덜어내고 최대한 공통되는 핵심만 남기는 것이라고 생각하게 되었다.
추상화 : 클래스간의 공통점을 찾아내서 공통의 조상을 만드는 작업
구체화 : 상속을 통해 클래스를 구현, 확장하는 작업
그런 의미에서 추상클래스는 클래스를 구성하는 요소 중 공통적으로 딱 필요한 핵심만 들어있는 클래스라고 할 수 있다. 핵심만 들어있다는 것은 해당 클래스를 구성하는 데 꼭 필요한 멤버 변수와 메서드만 들어있다는 말이라고 볼 수 있다.
우리가 핵심 요약을 할 때 길어지는 설명은 덜어내고 요약을 한다. 그런 것처럼 추상클래스에도 메서드의 세세한 구현부는 들어 있지 않다. 즉 메서드의 원형만 있고 내용물은 없는 것이다.
그렇기 때문에 추상클래스로 객체를 생성할 수는 없다. 만약 생성이 가능하다고 해도 메서드의 내용물이 없기 때문에 의미가 없다. 그래서 메서드의 자세한 동작은 추상클래스를 상속받는 자식 클래스에서 해야 한다.
추상클래스는 새로운 클래스를 작성하는데 있어서 바탕이 되는 조상클래스로서 중요한 의미를 갖는다.
새로운 클래스를 작성할 때 아무것도 없는 상태보다는 완전하지 못하더라도 어느 정도 틀을 갖춘 상태에서 시작하는 것이 나을 것이다. 이 틀을 이용하면 비슷한 기능을 가진 클래스들을 만들 때 같은 멤버 변수와 메서드를 사용하기 때문에 코드에 어느 정도 통일성을 줄 수 있다.
abstract class A {}
abstract
키워드를 붙이기만 하면 해당 클래스를 추상클래스로 만들 수 있다./* 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다. */
absctrac 리턴타입 메서드이름();
위와 같은 형태로 만들 수 있다. 구현부를 작성하지 않기 때문에 메서드이름까지만 작성하고 ;
을 붙여 문장을 끝낸다.
추상클래스를 상속받는 자식클래스는 오버라이딩을 통해 추상메서드를 모두 구현해 주어야 한다. 만약 하나라도 구현하지 않으면 그 자식 클래스 역시 추상클래스로 지정해 주어야 한다.
interface 인터페이스 이름 {
// public static final 생략 가능
public static final 타입 상수이름 = 값;
// public abstract 생략 가능
public abstract 리턴타입 메서드이름(매개변수목록);
public static final
이어야 하며, 이를 생략할 수 있다.public abstract
이어야 하며, 이를 생략할 수 있다. 단, static 메서드
와 디폴트 메서드
는 예외(JDK 1.8부터)interface Moveable {
void move(int x, int y); // 지정된 위치(x, y)로 이동하는 메서드
}
interface Attackable {
void attack(Unit u); // 지정된 대상(Unit)을 공격하는 메서드
}
interface Fightable extends Movable, Attackable {}
Fightable
인터페이스는 Movable
과 Attackable
인터페이스의 메서드들을 모두 멤버로 갖게 된다. implements
키워드를 사용한다. class 클래스이름 implements 인터페이스이름 {
// 인터페이스에 정의된 추상메서드를 구현해야 함
}
abstract
를 붙여서 추상클래스로 선언해야 한다.class Fighter extends Unit implements Fightable {}
인터페이스의 이름에는 주로 '~을 할 수 있는'의 의미인 'able'이 많이 쓰이는데, 그 이유는 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위해서이다. 또한 그 인터페이스를 구현한 클래스는 '~를 할 수 있는' 능력을 갖추었다는 의미이기도 하다. 이름이 'able'로 끝나는 것은 인터페이스라고 추측할 수 있지만 모든 인터페이스의 이름이 반드시 'able'로 끝나는 것은 아니다.
Fightable f = (Fightable) new Fighter();
or
Fightable f = new Fighter();
void attack(Fightable f) {}
Fightable method() {
return new Fighter();
}
디폴트 메서드(default method)
가 추가되었다. 디폴트 메서드(default method)
는 추상메서드의 기본적인 구현을 제공하는 메서드로 추상메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다. interface MyInterface {
void method();
default void newMethod() {}
}
이렇게 쓰면 해당 인터페이스를 구현한 클래스들을 변경하지 않아도 된다.
추상메서드의 기본 구현을 제공하기 때문에 중괄호 {}
를 꼭 써줘야 한다.
단, 새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생하는데, 이를 해결하는 규칙은 다음과 같다.
- 여러 인터페이스의 디폴트 메서드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
- 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.