-실체 클래스가 공통적으로 가져야 할 필드와 메소드들을 정의해놓은 추상적인 클래스이다
-실체 클래스의 멤버를 통일하는 데 목적이 있다
-모든 실체들이 가지고 있는 메소드의 실행 내용이 동일하다면 추상 클래스에 메스드를 작성하는 것이 좋다
실체 클래스를 설계하는 사람이 여러 사람일 경우, 실체 클래스마다 필드와 메소드가 제각기 다른 이름을 가질 수 있다
예를 들어 위 그림처럼 소유자의 이름을 저장하는 필드를 Telephone클래스에서는 owner라고 하고, SmartPhone 클래스에서는 user라고 할 수 있다
그리고 전원을 켜는 메소드를 Telephone에서는 turnOn()으로 설계하고 SmartPhone에서는 powerOn()이라고 설계할 수 있다
이렇게 데이터와 기능이 모두 동일함에도 불구하고 이름이 다르다 보니, 객체마다 사용 방법이 달라진다.
이 방법보다는 Phone이라는 추상 클래스에 소유자인 owner필드와 turnOn() 메소드를 선언하고, Telephone과 smartPhone은 Phone을 상속함으로써 필드와 메소드 이름을 통일할 수 있다
공통적인 필드와 메소드는 추상 클래스인 Phone에 모두 선언해두고, 다른 점만 실체 클래스에 선언하면 실체 클래스를 작성하는 데 시간을 절약할 수 있다
다음 그림을 보면 Telephone과 SmartPhone은 Phone을 상속받기 때문에 owner 필드와 turnOn()메소드를 선언할 필요가 없다
Telephone과 SmartPhone의 추가적인 특성인 autoAnswering()과 internetSearch()메소드만 각각 선언하면 된다
-추상 클래스를 선언할 때에는 클래스 선언에 abstract 키워드를 붙여야 한다
-abstract를 붙이면 new 연산자를 이용해서 객체를 만들지 못하고, 상속을 통해 자식 클래스만 만들 수 있다
public abstract class 클래스 {
//필드
//생성자
//메소드
}
-추상 클래스는 실체 클래스의 공통되는 필드와 메소드를 추출해서 만들었기 때문에 객체를 직접 사용할 수 없다
-추상 클래스는 새로운 실체 클래스를 만들기 위해 부모 클래스로만 사용된다
코드로 설명하자면 Animal클래스가 추상클래스라고 했을 때,
Animal animal = new Animal(); //X(객체 직접 사용할 수 없음)
Class Ant extends Animal { //O(부모 클래스로만 사용)
...
}
추상 클래스도 일반 클래스와 마찬가지로 필드, 생성자, 메소드를 선언할 수 있다
new 연산자로 직접 생성자를 호출할 수는 없지만 자식 객체가 생성될 때 super(...)를 호출해서 추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 한다
예제
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("폰 전원을 끕니다.");
}
}
위 코드는 Phone클래스를 추상 클래스로 선언한 것이다.
package com.javatest.java;
public class SmartPhone extends Phone{//실체 클래스
//생성자(추상클래스 Phone의 생성자 호출)
public SmartPhone(String owner){
super(owner);
}
//메소드
public void internetSearch(){
System.out.println("인터넷 검색을 합니다.");
}
}
위 코드는 Phone추상 클래스를 상속해서 SmartPhone 자식 클래스를 정의한 것이다.
SmartPhone클래스의 생성자를 보면 super(owner); 코드로 Phone의 생성자를 호출하고 있다
public class PhoneExample {//실행 클래스
public static void main(String[] args){
SmartPhone smartPhone = new SmartPhone("예진");
smartPhone.turnOn();//Phone의 메소드
smartPhone.internetSearch();
smartPhone.turnOff();//Phone의 메소드
}
}
메소드의 선언만 통일하고, 실행 내용은 실체 클래스마다 달라야 하는 경우가 있다
예를 들어 모든 동물은 소리를 내기 때문에 Animal이라는 추상클래스에서 sound()라는 메소드를 정의했다고 가정해보겠다
그렇다면 어떤 소리를 내도록 해야 하는데, 이것은 실체 클래스에서 직접 작성해야 될 부분이다 왜냐하면 동물은 다양한 소리를 내므로 이것을 추상 클래스에서 통일적으로 작성할 수 없기 때문이다.
public abstract class Animal {
//메소드
public void Sound(){
//동물은 다양한 소리를 내므로 추상 클래스에서 통일적으로 작성할 수 없음
}
}
그렇다고 해서 sound() 메소드를 실체 클래스에서 작성하도록 하면 sound()메소드를 잊어버리고 작성하지 않을 경우 동물은 소리를 낸다는 것에 위배된다.
이런 경우를 위해 추상 클래스는 추상 메소드를 선언할 수 있다
추상 메소드는 abstract 키워드와 함께 메소드의 선언부에만 있고, 메소드 실행 내용인 중괄호 {}가 없는 메소드를 말한다
추상메소드 선언 방법
[public : protected] abstract 리턴타입 메소드이름(매개변수, ...);
추상 클래스 설계 시 하위 클래스가 반드시 실행 내용을 채우도록 강제하고 싶은 메소드가 있을 경우 해당 메소드를 추상 메소드로 선언한다.
자식 클래스는 반드시 추상 메소드를 재정의해서 실행 내용을 작성해야 하는데, 그렇지 않으면 컴파일 에러가 발생한다
public abstract class Animal {
public abstract void sound();
}
위 코드는 Animal 클래스를 추상 클래스로 선언하고 sound()메소드를 추상 메소드로 선언한 것이다.
어떤 소리를 내는지는 결정할 수 없지만 동물은 소리를 낸다는 공통적인 특징을 규정하기 위해 sound() 메소드를 추상 메소드로 선언했다
Animal클래스를 상속하는 하위 클래스는 동물마다 고유한 소리를 내도록 sound() 메소드를 재정의해야 한다
예를 들어 Dog는 "멍멍", Cat은 "야옹" 소리를 내도록 Dog와 Cat 클래스에서 sound() 메소드를 재정의해야 한다
예제
public abstract class Animal {//추상 클래스
public String kind;
public void breathe() {
System.out.println("숨을 쉽니다.");
}
public abstract void sound();//추상 메소드
}
추상 메소드 선언해주었다
public class Dog extends Animal {
public Dog() {
this.kind = "포유류";
}
@Override
public void sound() { //추상 메소드 재정의
System.out.println("멍멍");
}
}
위 Dog클래스는 추상 클래스인 Animal을 상속하고, 추상 메소드인 sound()를 재정의했다.
만약 재정의한 부분을 주석 처리하면 컴파일 에러가 발생한다
public class Cat extends Animal{
public Cat(){
this.kind = "포유류";
}
@Override
public void sound(){ //추상 메소드 재정의
System.out.println("야옹");
}
}
위 Cat클래스도 추상 클래스인 Animal을 상속하고, 추상 메소드인 sound()를 재정의했다
public class AnimalExample {
public static void main(String[] args){
//1
Dog dog = new Dog();
Cat cat = new Cat();
dog.sound();
cat.sound();
System.out.println("------");
//2
Animal animal = null;
animal = new Dog();
animal.sound();
animal = new Cat();
animal.sound();
System.out.println("-------");
//3
animalSound(new Dog());
animalSound(new Cat());
}
public static void animalSound(Animal animal){
animal.sound();
}
}
sound()메소드를 호출하는 방법을 세 가지 방법으로 표현해보았다
1.가장 일반적인 방법으로 Dog와 Cat 변수로 호출했다
2.Animal 변수로 타입 변환해서 sound()메소드를 호출했다
자식은 부모 타입으로 자동타입 변환이 될 수 있고, 메소드가 재정의되어 있을 경우 재정의된 자식 메소드가 호출되는 다형성의 특징이 그대로 적용된다
3.부모 타입의 매개 변수에 자식 객체를 대입해서 메소드의 다형성을 적용했다
이것은 2와 같은 원리로 자식 객체가 부모 타입으로 자동 타입 변환되어 재정의된 sound()메소드가 호출된다