추상 클래스

goose_bumps·2024년 5월 20일

Java

목록 보기
9/21

추상이란? 사전적 의미로는 구체적인 표상이나 개념에서 공통된 성질을 뽑아 이를 일반적인 개념으로 파악하는 정신 작용을 말한다. 이 의미를 잘 기억할 필요가 있다.

1. 추상 클래스

추상 클래스는 미완성된 메서드인 추상메서드를 포함하고 있는 클래스이다. 추상클래스는 미완성 클래스라 할 수 있어 인스턴스를 생성할 수 없다.
abstract 키워드를 붙이기만 하면 추상 클래스가 된다. abstract을 붙여주는 이유는 클래스 내에 추상 메서드가 있는 것을 이 키워드를 보고 알고, 상속을 통해 구현해주어야 한다는 것을 알기 위해서이다.

abstract class example{
	abstract void exampleMethod();
    }

추상 메서드가 없어도 abstract을 붙여 추상 클래스로 지정할 수 있다. 추상 메서드가 없는 추상 클래스라고 해도 인스턴스 생성을 불가능하다.

2. 추상 메서드

메서드는 선언부와 구현부로 구성되어 있다. 추상 메서드는 구현부 없이 선언부만 있는 메서드로 중괄호가 존재하지 않으며 미완성 메서드라 할 수 있다.

/*어떤 기능을 수행할 목적인지 주석을 통해 설명*/ 
abstract void method();

앞에서 설명했지만, 이 추상 메서드가 있는 클래스 앞에는 abstract을 붙여 추상 클래스가 된다. 추상 메서드를 가진 추상 클래스를 상속하는 자손 클래스들은 반드시 모든 추상 메서드를 구현 즉, 오버라이딩 해야 한다. 하지만, 추상 클래스의 추상 메서드 외에 일반 메서드는 오버라이딩이 필수가 아니다.

abstract class Parent{
	int a;
	abstract void method1();
    abstract void method2();
    void method3();
    }
class Child extends Parent{
	void method1(){/*내용*/}
    void method2(){/*내용*/}

추상 클래스에는 추상 메서드만 있는게 아닌 일반 메서드와 멤버도 존재할 수 있다. 위 예시에는 추상 메서드가 2개가 있는데 자손 클래스는 2개의 추상 메서드를 모두 구현했다.
하지만, 만약 2개 다 구현하지 않고 1개만 구현했다면 그 자손 클래스 앞에 abstract을 붙여 추상 클래스로 만들어줘야 한다. 그러지 않으면 컴파일 에러가 발생한다.

abstract class Parent{
	int a;
	abstract void method1();
    abstract void method2();
    void method3();
    }
abstract class Child extends Parent{
	void method1(){/*내용*/}
    void method3(){/*내용*/}
  • 추상 클래스를 상속하는 자손 클래스는 추상 메서드를 모두 구현해야 한다
  • 모두 구현하지 않을 경우 그 클래스 앞에 abstract을 추가하여 추상 클래스로 만들어야 한다

그럼 여기서 의문이 든다.
굳이 추상 클래스가 아니어도 일반 클래스의 메서드를 오버라이딩해도 되는데도 불구하고 왜 추상 클래스를 사용할까?
바로 "강제성" 때문이다.
강제성이 없다면 자손 클래스에서는 이 메서드가 구현되어 있는 것으로 알고 있을 수 있다. 하지만, 추상 클래스로 정의해놓으면 자손 클래스에서는 추상 메서드이므로 이 내용을 반드시 구현해주어야 한다는 것으로 인식할 수 있기 때문이다.

3. 추상클래스의 참조변수 배열

한 가지 예제를 만들어보겠다. 이어폰이라는 추상클래스를 A,B,C 타입의 제품 클래스가 상속한다. 세 제품 모두 출력은 동일하나 가격과 색상은 각각 다르다.

abstract class earphone{
	abstract void price();
	abstract void color();
	void output() {System.out.println("30dB");}
}

class Atype extends earphone{
	void price() {System.out.println("3,000$");}
	void color() {System.out.println("white");}
}

class Btype extends earphone{
	void price() {System.out.println("2,000$");}
	void color() {System.out.println("black");}
}

class Ctype extends earphone{
	void price() {System.out.println("3,400$");}
	void color() {System.out.println("pink");}
}

여기서 price와 color 메서드는 추상 메서드이기 때문에 earphone 클래스를 상속하는 자손 클래스는 자신의 클래스에 알맞게 반드시 구현해야 한다.
반대로, output메서드는 동일하기 때문에 필수적으로 구현할 필요가 없다.
이를 다르게 말하면, 공통적인 부분은 일반 메서드로 만들고 클래스마다 다른 특성을 가지게 해야 하는 메서드는 추상메서드로 정의하면 되는 것이다.

이제 이 자손 클래스를 참조배열에 대입해보자.

		earphone[] group = new earphone[3];
		group[0] = new Atype();
		group[1] = new Btype();
		group[2] = new Ctype();
		for(int i = 0; i < group.length; i++) {
			group[i].price();
			group[i].color();

earphone 클래스 타입의 참조배열에 각기 다른 클래스의 객체를 같이 보관할 수 있는 이유는 공통 조상을 상속하였기 때문이다.

공통 조상인 earphone 클래스 타입의 참조변수 배열을 통해서 서로 다른 종류의 인스턴스를 하나로 묶은 것이다.
반복문을 사용하여 earphone 클래스의 추상 메서드 price, color를 호출하는 것처럼 보이지만 실제로는 각 인스턴스에서 구현된 메서드가 호출되는 것이다.

그렇다면, 모든 클래스가 상속하는 공통 조상인 Object 타입으로 참조배열을 생성하면 어떨까?

		Object[] group = new Object[3];
		group[0] = new Atype();
		group[1] = new Btype();
		group[2] = new Ctype();
		for(int i = 0; i < group.length; i++) {
			group[i].price(); //불가능
			group[i].color(); //불가능

모든 클래스가 Object를 상속하기 때문에 참조배열로 서로 다른 인스턴스들을 묶을 수 있지만, price와 color 메서드는 Object 클래스에 정의되어 있지 않기 때문에 호출 불가능하다.

0개의 댓글