본문 바로가기
프로그래밍언어/Java

07-3 추상 클래스

by 스꼬맹이브로 2021. 4. 28.
728x90
반응형
SMALL

먼저, 추상(abstract)라는 단어의 사전적 의미는 실체 간에 공통되는 특성을 추출한 것을 말한다.

예를 들어서, 붕어, 잉어, 배스, 연어, 가자미, 고등어 등은 물고기라는 공통점이 있다. 여기서 물고기는 구체적인 실체라기보다는 실체들의 공통되는 특성을 가지고 있는 추상적인 것이라고 볼 수 있다.

이런 개념을 도입한 것이 추상 클래스이다.

객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다면 이 클래스들의 공통적인 특성을 추출해서 선언한 클래스를 추상 클래스라고 한다.

추상 클래스와 실체 클래스는 상속의 관계를 가지고 있으며, 추상 클래스가 부모, 실체 클래스가 자식으로 구현되어 실체 클래스는 추상 클래스의 모든 특성(필드, 메소드)을 물려받고, 추가적인 특성을 가질 수 있다.

 

그렇다면 추상클래스를 만드는 이유는 무엇일까?

그 이유는 두 가지가 있다.

 

1. 공통된 필드와 메소드의 이름을 통일할 목적으로 사용

실체 클래스를 설계하는 사람이 여러 사람일 경우, 실체 클래스마다 필드와 메소드가 제각기 다른 이름을 가질 수 있다.

다음 그림을 참고하여 살펴 보자.

Telephone Class와 SmartPhone Class의 필드와 메소드는 같은 역할을 하지만 이름이 다르다. 다음과 같이 동작하는 기능와 사용하는 데이터가 모두 동일함에도 불구하고 이름이 다르다 보니, 객체마다 사용 방법이 달라지는 경우가 발생할 수 있다. 때문에 추상클래스의 메소드를 상속받음으로써 필드와 메소드 이름을 통일할 수 있다.

2. 실체 클래스를 작성할 때 시간이 절약된다

공통적인 필드와 메소드는 추상 클래스에 모두 선언해두고, 다른 점만 실체 클래스에 선언하면 실체 클래스를 작성하는 데 시간을 절약할 수 있다.

다음과 같이 공통된 필드와 메소드 이외에 Telephone의 고유한 기능인 autoAnswering()메소드를 선언하고, SmartPhone의 고유한 기능인 internetSearch()메소드를 선언하면 된다. 

 

<추상 클래스 선언>

이제 실제로 선언하는 법에 대해서 알아보자. 

추상 클래스를 선언할 때에는 클래스 선언에 abstract 키워드를 붙여야 한다. abstract를 붙이면 new 연산자를 이용해서 객체를 만들지 못하고, 상속을 통해 자식 클래스만 만들 수 있다.

public abstract class 클래스{
 //필드
 //생성자
 //메소드
}

추상 클래스도 일반 클래스와 마찬가지로 필드, 생성자, 메소드 선언을 할 수 있으며, new 연산자로 직접 생성자를 호출할 수는 없지만 자식 객체가 생성될 때 super()를 호출해서 추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 한다.

 

다음 예제는 앞에서 설명한 phone 클래스를 선언한 것이다.

public abstract class Phone {
	//필드
	public String owner;
	
	//생성자
	public Phone(String owner) {
		this.owner = owner;
	}
	
	//메소드
	public void turnOn() {
		System.out.println("폰 전원을 켭니다.");
	}	
	public void turnOff() {
		System.out.println("폰 전원을 끕니다.");
	}
}

다음은 위의 추상클래스를 사용하여 SmartPhone 자식 클래스를 정의한 것이다.

public class SmartPhone extends Phone {
	//생성자
	public SmartPhone(String owner) {
		super(owner);
	}
	//메소드
	public void internetSearch() {
		System.out.println("인터넷 검색을 합니다.");
	}
}

다음 PhoneExample클래스의 4라인은 Phone의 생성자를 호출해서 객체를 생성할 수 없음을 보여주며, 그 대신에 자식 클래스인 SmartPhone으로 객체를 생성해서 turnOn()메소드와 turnOff()메소드를 사용하는 예제이다.

public class PhoneExample { 

	public static void main(String[] args) {
		//Phone phone = new Phone(); (x)
		
		SmartPhone smartPhone = new SmartPhone("홍길동");
		
		smartPhone.turnOn();
		smartPhone.internetSearch();
		smartPhone.turnOff();
	}
}

결과:

여기서 Phone의 생성자를 호출해서 객체를 생성할 수 없는 이유는 당연하게도 추상 클래스이기 때문인데, 추상 클래스는 부모 클래스로만 사용이 가능하고 코드로는 extends 뒤에만 올수 있는 클래스이다.

 

<추상 메소드와 재정의>

앞서 말했다시피, 추상 클래스는 실체 클래스의 멤버를 통일하는 데 목적이 있다.

하지만 메소드의 선언만 통일하고, 실행 내용은 실체 클래스마다 달라야 하는 경우가 발생하기도 한다.

예를 들어, 모든 동물의 소리를 구현하기 위해 sound()라는 메소드를 정의해야 되는데, 동물들의 소리가 가지각색이라 실체 클래스에서 직접 작성해야 된다. 그렇다고 해서 sound()메소드를 실체 클래스에서 작성하도록 하면 sound()메소드를 잊어버리고 작성하지 않을 경우 동물은 소리를 낸다는 것에 위배된다. 이러한 경우를 위해 추상 클래스에서는 추상 메소드를 선언할 수 있다. 추상 메소드란 abstract 키워드와 함께 메소드의 선언부만 있고 메소드의 실행 내용인 중괄호{}가 없는 메소드를 말한다. 예를 들었던 sound()메소드를 선언하면 다음과 같다.

public abstract class Animal{
  public abstract void sound();
}

다음 그림을 구현하면서 다시 한번 살펴 보자.

추상 메소드인 Animal Class에는 숨을 쉬는 것과 소리를 내는 메소드가 들어가야 하는데, 소리는 어떤 소리를 내는지 모르기 때문에 추상 메소드로 구현해야 한다.

이를 코드로 구현하면 다음과 같다.

public abstract class Animal { //추상 클래스
	public void breathe() {
		System.out.println("숨을 쉽니다.");
	}

	public abstract void sound(); //추상 메소드
}

다음으로 Dog Class와 Cat Class를 구현한 코드이다.

public class Dog extends Animal {
	@Override
	public void sound() {
		System.out.println("멍멍");
	}
}
public class Cat extends Animal {
	@Override
	public void sound() {
		System.out.println("야옹");
	}
}

마지막으로 Dog와 Cat객체를 사용하여 3가지 방법으로 호출한 코드이다.

public class AnimalExample {
	public static void main(String[] args) {
		//1.
		Dog dog = new Dog();
		Cat cat = new Cat();
		dog.sound();
		dog.breathe();
		cat.sound();
		cat.breathe();
		System.out.println("-----");
		
		//2.변수의 자동 타입 변환
		Animal animal = null;
		animal = new Dog();
		animal.sound();
		animal.breathe();
		animal = new Cat();
		animal.sound();
		animal.breathe();
		System.out.println("-----");
		
		//3.매개변수의 자동 타입 변환
		animalSound(new Dog());
		animalSound(new Cat());
		animalBreath(new Dog());
		animalBreath(new Cat());
	}
	
	public static void animalSound(Animal animal) {
		animal.sound();
	}
	public static void animalBreath(Animal animal) {
		animal.breathe();
	}
}

결과:

  1. 가장 일반적인 방법으로 Dog와 Cat 변수로 호출
  2. Animal 변수로 타입 변환해서 메소드를 호출. 자식은 부모 타입으로 자동 타입 변환이 될 수 있고, 메소드가 재정의되어 있을 경우 재정의된 자식 메소드가 호출되는 다형성의 특징이 그대로 적용됨.
  3. 부모 타입의 매개 변수에 자식 객체를 대입해서 메소드의 다형성을 적용.(2와 같은 원리)
728x90
반응형
LIST

'프로그래밍언어 > Java' 카테고리의 다른 글

08-2 타입 변환과 다형성  (0) 2021.06.28
08-1 인터페이스  (0) 2021.05.18
07-2 타입 변환과 다형성  (0) 2021.03.09
07-1 상속  (0) 2021.02.25
06-6 패키지와 접근 제한자  (0) 2021.02.23