추상클래스(Abstract)
추상클래스는 미완성된 메서드(추상메서드)를 포함하고 있는 미완성된 클래스라고 할 수 있습니다. 미완성된 클래스로는 인스턴스를 생성할 수 없기 때문에 추상클래스는 상속을 통해서 자손클래스에 의해서 완성이 된 후 사용할 수 있습니다.
추상클래스를 이용하면 그 자체로는 클래스로서의 역할을 하지 못하지만, 새로운 클래스를 작성할 때 일종의 설계도 역할을 하여 조상클래스로서 중요한 의미를 가집니다.
추상클래스 정의
클래스 선언부 앞에 'abstract' 키워드를 붙여서 선언합니다. (접근제어자보다 뒤에 선언해야 합니다. 예를 들어, public abstract class 클래스명으로 선언해야 합니다.)
abstract class ExampleAbstract {}
추상클래스도 멤버를 가질 수 있습니다. 그리고 추상메서드 뿐만 아니라 일반 메서드(몸통이 있는 메서드)도 가질 수 있습니다. 아래 코드를 통해 추상메서드에 대한 다양한 특징을 살펴보면 좋을 것 같습니다.
public abstract class ExampleAbstract {
String iv1;
int iv2;
ExampleAbstract(){
iv1 = "hello";
iv2 = 100;
}
abstract void method1();
abstract String method2();
void method3(){ // 일반 메서드
System.out.println("I'm default method.");
System.out.println(method2()); // 추상 메서드 호출 가능
}
}
class ExampleChild1 extends ExampleAbstract{
@Override
void method1() {}
@Override
String method2() {
return null;
}
}
// abstract 클래스로 abstract 클래스 상속 가능 --> 부모 메서드의 일부 메서드만 override 할 수 있다.
abstract class ExampleChild2 extends ExampleAbstract{
@Override
void method1() {}
}
추상클래스는 추상메서드가 존재한다는 부분에서만 차이가 있을 뿐이지, 그 외에는 일반 클래스와 다를 게 없습니다. 따라서 자손 클래스의 다중 상속도 금지되고, 추상클래스 타입의 참조변수로 자손 클래스 타입의 객체를 참조할 수도 있는 등 일반 클래스의 특징을 그대로 가지고 있다고 생각하시면 됩니다.
인터페이스 (Interface)
일종의 추상클래스이지만, 추상클래스보다는 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 혹은 멤버변수를 멤버로 가질 수 없습니다. 즉, 추상메서드와 상수만을 멤버로 가질 수 있는 클래스입니다. (JDK1.8 버전부터는 몸통을 갖춘 일반 메서드도 가질 수 있습니다.)
또한, 추상클래스와 달리 다중 상속이 가능하여 하나의 자손 클래스에서 여러 개의 인터페이스를 상속받아 구현할 수 있습니다.
즉, 위에서 추상 클래스는 부분적으로만 미완성된 상태를 가질 수 있었습니다. 하지만 인터페이스는 완성된 부분이 없는, 상속받은 클래스가 처음부터 다 구현해야 하는 '기본 설계도'라고 할 수 있겠습니다.
인터페이스 사용 시 제약사항
1. 모든 멤버변수는 public static final 이어야 하고, 이는 생략 가능합니다.
2. 모든 메서드는 public abstract 이어야 하고, 이는 생략 가능합니다.
- JDK1.8 버전부터는 인터페이스 내에 static 메서드와 default 메서드도 허용하는 방향으로 변경되었습니다. 이들 메서드는 public abstract 키워드를 생략할 수 없습니다.
예제 코드로 interface에 대해서 알아보겠습니다.
public interface ExampleInterface {
public static final int constants = 1;
public abstract void method();
void method2(); // public abstract void method2()
default method3(){} // 일반 메서드
}
abstract class ExampleAbstract implements ExampleInterface{ // 인터페이스의 일부만 구현할 때는 abstract로 만든다.
@Override
public void method(){
}
}
class ExampleChild implements ExampleInterface{
@Override
public void method() {
}
@Override
public void method2() { // 부모보다 넓은 접근 제어자
}
}
추가로, 위 코드에서 보이듯이 자손 클래스가 부모 클래스의 메서드를 오버라이딩 시, 부모 클래스 메서드보다 넓은 범위의 접근 제어자를 지정해야 한다.
인터페이스와 다형성
인터페이스와 다형성을 묶어서 생각하는 부분이 저에게는 낯설었는데요. 인터페이스와 다형성에 대해서 자세히 알아보겠습니다.
인터페이스를 implements 키워드를 이용하여 구현한다는 것은 결국 그 인터페이스를 '상속'한다는 의미와 같다고 볼 수 있습니다. 즉, 인터페이스를 구현한 클래스의 조상 클래스가 인터페이스라 할 수 있기 때문에 해당 인터페이스 타입의 참조변수로 자식 클래스의 인스턴스를 참조할 수 있으며, 따라서 인터페이스 타입으로 형변환 또한 가능합니다.
추상 클래스와 달리 인터페이스는 다중 상속이 가능하여 하나의 클래스가 여러 개의 인터페이스를 'implements'할 수 있습니다.
예시로 알아보겠습니다.
public interface ExampleInterface {
public static final int constant = 1;
int constant2 = 0;
public abstract void method(ExampleInterface ex);
ExampleInterface method2();
}
class ExampleChildClass implements ExampleInterface{
@Override
public void method(ExampleInterface ex){
}
@Override
public ExampleInterface method2() {
ExampleChildClass childClass = new ExampleChildClass();
return childClass;
}
}
1. method(ExampleInterface ex) : method(new ExampleChildClass()) 로 넘겨줄 수 있습니다.
2. ExampleInterface method2(): 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미합니다.
아래 예시를 통해 다형성을 테스트 해 보겠습니다.
public interface Readable {
void readDocument(String title);
}
class Person {
static Readable doWork(String type){
if(type.equals("book")){
return new Book();
}
else if(type.equals("newspaper")){
return new Newspaper();
}
else return null;
}
}
class Book implements Readable{
@Override
public void readDocument(String title) {
System.out.println("I'm reading a book: " + title);
}
}
class Newspaper implements Readable{
@Override
public void readDocument(String title) {
System.out.println("I'm reading a newspaper: " + title);
}
}
Person.doWork("book").readDocument("myBook");
Person.doWork("newspaper").readDocument("myNewspaper");
[실행결과]
I'm reading a book: myBook
I'm reading a newspaper: myNewspaper
추상클래스와 인터페이스에 대해서 알아보았습니다. 두 개는 비슷한 역할을 하는 것처럼 보이나 내부적으로는 약간의 차이가 존재함을 확인했습니다. 예를 들어, 추상클래스에서의 다중 상속의 한계를 인터페이스를 통해 해결할 수 있습니다.
두 가지 클래스를 잘 이용하면 객체지향에서 추구하는 추상화를 잘 구현할 수 있기 때문에 코드를 작성할 때 클래스마다 겹치는 부분이 있는지, 그 부분을 어떻게 따로 추출하여 추상화할 수 있는지에 대한 고민하는 것이 중요할 것 같습니다.
'Java' 카테고리의 다른 글
Comparable과 Comparator (4) | 2023.10.20 |
---|---|
다형성(Polymorphism) (2) | 2023.10.15 |
제어자 (static과 final) (0) | 2023.10.15 |
JVM의 메모리 구조 (0) | 2023.10.14 |
가변인자(varargs) (0) | 2023.10.14 |