본문 바로가기

Java

[JAVA]추상 클래스의 개념과 abstract 키워드

사전적 의미로 추상은 실체 간에 공통되는 특성을 추출한 것을 말한다. 예를 들어 새, 물고기, 곤충 등의 실체에서 공통되는 특성을 추출해보면 동물이라는 결과가 나온다. 추상 클래스도 다음과 같이 생각해서 접근하면 된다.

우선, 객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다. 그리고 여러 실체 클래스들의 공통적인 특성을 추출해서 선언한 클래스를 추상 클래스라고 한다. 이러한 특성 때문에 추상 클래스와 실체 클래스는 상속의 관계를 가지고 있다.

예를 들어 새 클래스, 곤충 클래스, 물고기 클래스라는 실체 클래스가 있다고 해보자. 이 클래스들에서 공통된 필드와 메소드들을 모아서 따로 동물 클래스를 만들었다고 하면 동물 클래스는 새, 곤충, 물고기 클래스의 추상 클래스가 되고 상속 관계에 놓인다.

추상 클래스는 실체 클래스의 공통되는 필드와 메소드를 추출해서 만든 것이다. 따라서 객체를 직접 생성해서 사용할 수 없다.

Animal animal = new Animal(); //이런 식의 객체 생성이 안된다.

그렇다면 추상 클래스는 어떨 때 사용할까? 추상 클래스는 실체 클래스를 만들기 위한 부모 클래스로만 사용된다.

class Cat extends Animal { } //이런식으로 말이다.

추상 클래스의 용도

왜 굳이 이렇게 실체 클래스들의 공통된 특성 뽑아내 추상 클래스로 만드는 것인지 알아보자. 크게 두 가지의 이유가 있다.

  • 실체 클래스들의 공통된 필드와 메소드의 이름을 통일한다.
    • 실제로 대부분 개발은 혼자 하지 않는다. 협업을 통해서 이루어지는데, 그 경우 클래스를 설계하는 사람이 한 사람이 아닐 수 있다. 이 경우 실체 클래스마다 이름이 다른 필드와 메소드가 만들어질 수 있다. 동일한 기능을 함에도 불구하고 다른 이름을 사용하면 객체마다 사용 방법이 달라져 불편해진다.
  • 실체 클래스 작성 시 시간 절약

추상 클래스 선언

이제 추상 클래스를 선언해보자. 추상 클래스를 선언할 때에는 클래스 선언에 abstract 키워드를 붙인다.

  • abstract 키워드
    • new 연산자를 이용해 객체를 만들 수 없어진다.
    • 상속을 통해 자식 클래스를 만드는 것만 가능하다.
public abstract class Example { }

다음 예시를 통해 실제로 추상 클래스를 선언해 보도록 하자.

Car.java

public abstract class Car {//추상 클래스

    public String model;//필드
    public Car(String model) {//생성자
        this.model = model;
    }
    public void carOn() {
        System.out.println("시동을 겁니다.");
    }
    public void showModel() {
        System.out.println("모델: " + model);
    }
    public void carOff() {
        System.out.println("시동을 끕니다.");
    }
}

SmartCar.java

public class SmartCar extends Car{

    public SmartCar(String model) {//생성자
        super(model);//추상 클래스의 생성자가 매개값을 필요로 하므로 super로 전달
    }
    public void autoBreak() {
        System.out.println("위험이 감지되어 자동 브레이크가 작동합니다.");
    }   
}

Main.java

public class Main {
    public static void main(String[] args) {
        //Car car = new Car("현대"); Car는 추상 클래스이므로 new 연산자를 이용해 객체를 만들지 못한다.
        SmartCar smartCar = new SmartCar("현대");//SmartCar는 추상 클래스가 아니므로 new 연산자로 객체를 만들 수 있다.

        smartCar.carOn();
        smartCar.showModel();
        smartCar.autoBreak();
        smartCar.carOff();
    }
}

Main 실행 결과

시동을 겁니다.
모델: 현대
위험이 감지되어 자동 브레이크가 작동합니다.
시동을 끕니다.

추상 메소드와 오버라이딩

추상 클래스에는 실체 클래스가 가져야 하는 공통된 필드와 메소드들을 정의해놓고 있다. 하지만 메소드의 선언만 통일화되고 실행 내용이 달라야 하는 경우가 있다. 예를 들어 추상 클래스인 Animal 클래스가 있다고 하자. Animal 클래스에 동물 울음소리를 출력하는 메소드인 sound()가 있다고 해보자. 이 경우 각 동물(예를 들어 고양이, 강아지, 돼지 등)들이 내는 울음소리가 다르므로 추상 클래스에서 통일된 메소드를 작성할 수 없다.

이러한 경우를 위해 추상 클래스는 추상 메소드를 선언할 수 있다. 추상 메소드는 추상 클래스에서만 선언할 수 있다. 추상 메소드는 메소드의 내용을 생략한 메소드이다. 메소드의 내용은 특정할 수 없지만 모든 동물은 울음소리를 낸다는 공통된 특징이 있으므로 sound() 메소드를 다음과 같이 추상 메소드로 선언할 수 있다.

//[접근제한자] abstract 리턴타입 메소드명(매개변수);
public abstract void sound();

그리고 Animal 클래스를 상속하는 하위 클래스(Dog 클래스, Cat 클래스 등)들은 sound()메소드를 재정의 해주면 된다. 다음 예시를 참고하자.

Animal.java

public abstract class Animal {
  public String name;
  public Animal(String name) {
    this.name = name;
  }
  public void showName() { System.out.println("이름: " + name); }
  public abstract void sound();//추상 메소드
}

Dog.java

public class Dog extends Animal{
  public Dog(String name) {
    super(name);
  }

  @Override//추상 메소드를 하위 클래스인 Dog 클래스에서 오버라이딩
  public void sound() {
    System.out.println("멍 멍!");
  }
}

Main.java

public class Main {
  public static void main(String[] args) {
    Dog dog = new Dog("멍뭉이");

    dog.showName();
    dog.sound();
  }
}

Main 실행 결과

이름: 멍뭉이
멍 멍!