이전 내용
[java] 클래스의 타입 변환, 다형성
이전 내용 [java] 접근 제한자, 클래스 상속이전 내용 [java] 정적 멤버와 Static, 싱글톤이전 내용 [java] java 예제 (메소드 오버로딩)이전 내용 [java] 메소드 선언 : 가변 인수 모를 때 (feat. enhanced for
puppy-foot-it.tistory.com
추상 클래스
[실체 클래스 vs 추상 클래스]
- 실체 클래스: 객체를 직접 생성할 수 있는 클래스
- 추상 클래스: 실체 클래스들의 공통적인 특성을 추출해서 선언한 클래스
▶ 추상 클래스와 실체 클래스는 상속의 관계를 가지고 있으며, 추상 클래스가 부모, 실체 클래스가 자식으로 구현되어 실체 클래스는 추상 클래스의 모든 특성을 물려받고, 추가적인 특성을 가질 수 있다. (특성: 필드, 메소드)
예. UsDollar.class, Krw.class, Jpy.class 등의 실체 클래스
실체 클래스들과 공통되는 필드와 메소드를 따로 선언한 Money.class - 추상 클래스
◆ 추상 클래스의 용도
1. 공통된 필드와 메소드의 이름 통일
: 데이터와 기능이 모두 동일한데 이름이 다르게 되면, 객체마다 사용 방법이 달라진다. 따라서 이 방법보다는 추상 클래스에 필드와 메소드를 선언하고, 실체 클래스들에서는 추상 클래스를 상속함으로써 필드와 메소드 이름을 통일할 수 있다.
2. 실제 클래스 작성 시 시간 절약
공통적인 필드와 메소드는 추상 클래스에 모두 선언해두고, 다른 점만 실체 클래스에 선언하면 실체 클래스를 작성하는 데 시간을 절약할 수 있다. 실무에서 PM(설게자)이 코더에게 클래스는 어떤 구조로 알려주어야 한다는 것을 알려줘야 하는데, 특히 코더가 작성해야 할 클래스가 다수이고 이 클래스들이 동일한 필드와 메소드를 가져야 할 경우, 문서로 전달하기 보다는 PM이 이 내용들을 추려내어 추상 클래스로 설계 규격을 만들고 코더에게 추상 클래스를 상속해서 실체 클래스로 만들도록 요청하면 된다.
◆ 추상 클래스 선언
추상 클래스 선언 시에는 클래스 선언에 abstract 키워드를 붙여야 한다.
이 abstract 키워드를 붙이게 되면 new 연산자를 이용해서 객체를 만들지 못하고, 상속을 통해 자식 클래스만 만들 수 있다.
public abstract class 클래스 {
//필드
//생성자
//메소드
}
자식 객체가 생성될 때 super()로 호출해서 추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 한다.
추상 메소드와 재정의
메소드의 선언만 통일하고, 실행 내용은 실체 클래스마다 달라야 하는 경우 추상 클래스는 추상 메소드를 선언할 수 있다. 추상 메소드는 abstract 키워드와 함께 메소드의 선언부만 있고 메소드 실행 내용인 중괄호{}가 없는 메소드를 말한다.
[public | protected] abstract 리턴타입 메소드이름(매개변수, ...);
추상 클래스 설계 시 하위 클래스가 반드시 실행 내용을 채우도록 강제하고 싶은 메소드가 있을 경우 해당 메소드를 추상 메소드로 선언한다. 이 경우 자식 클래스는 반드시 추상 메소드를 재정의해서 실행 내용을 작성해야 하며, 그렇지 않은 경우 컴파일 에러가 발생한다.
만약 동물로 예를 들면,
모든 동물은 소리를 내기 때문에 Animal 추상 클래스에서 sound()라는 메소드를 정의했는데, 동물마다 다양한 소리를 내므로 추상 클래스가 아닌 실체 클래스에서 직접 작성해야 한다. 그러나, 모든 실체 클래스에서 sound() 메소드를 작성해야 하기 때문에 sound() 메소드를 잊어버리지 않도록 강제해야 한다.
이러한 경우 추상 클래스에서 sound() 추상 메소드를 선언하면 된다.
그리고 추상 클래스를 상속하는 하위 클래스는 동물마다 고유한 소리를 내도록 sound() 메소드를 재정의해야 한다.
이를 코드로 표현하면 다음과 같다.
[Animal 추상 클래스와 sound() 추상 메소드 선언]
public abstract class Animal { // 추상 클래스
public String kind;
public void breathe() {
System.out.println("숨을 쉽니다.");
}
public abstract void sound(); // 추상 메소드
}
[하위 클래스: Dog 클래스]
public class Dog extends Animal {
public Dog() {
this.kind = "포유류";
}
@Override
public void sound() {
System.out.println("멍멍");
}
}
[하위 클래스: Cat 클래스]
public class Cat extends Animal {
public Cat() {
this.kind = "포유류";
}
@Override
public void sound() {
System.out.println("야옹");
}
}
▶ 하위 클래스인 Dog와 Cat 클래스는 추상 클래스인 Animal을 상속하고, 추상 메소드인 sound()를 각각의 클래스에 맞게 재정의했다. 만약 해당 부분이 없으면 컴파일 에러가 발생한다.
[출력 클래스: AnimalExample]
public class AnimalExample {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.sound();
cat.sound();
System.out.println("--------");
// 변수의 자동 타입 변환
Animal animal = null;
animal = new Dog();
animal.sound(); // 자동 타입 변환 및 재정의된 메소드 호출
animal = new Cat();
animal.sound(); // 자동 타입 변환 및 재정의된 메소드 호출
System.out.println("---------");
// 메소드의 다형성
animalSound(new Dog());
animalSound(new Cat());
}
public static void animalSound(Animal animal) {
animal.sound();
}
}
▶ sound() 메소드를 호출하는 방법은 세 가지 방식이 있는데,
1) Dog와 Cat 클래스의 인스턴스를 직접 생성하고, 각 객체에서 sound() 메소드를 호출
(장점)
- 코드가 간단하고 각 객체의 타입이 명확하여 이해하기 쉬워 보기가 좋고, 디버깅할 때도 용이
- 정적 타입 검사: 컴파일 시간에 타입이 결정되므로, 잘못된 타입에 대한 오류를 조기에 발견
(단점)
1: 확장성의 제한: 만약 새로운 동물 클래스가 추가되면, 메인 코드를 수정해야 하므로 확장성이 떨어진다. 각 동물에 대해 추가적인 객체 생성과 호출을 해야 한다.
2. 코드 중복: 각 동물의 sound() 메소드를 호출하는 코드가 중복되므로, 유지 보수가 어려워질 수 있다.
2) Animal 타입의 변수 animal에 Dog와 Cat 객체를 할당한 후, 각 객체의 sound() 메소드를 호출
(장점)
- 다형성: Animal 타입의 하나의 변수로 다양한 자식 객체를 다룰 수 있어 코드의 유연성이 증가. 향후 새로운 동물 클래스를 추가할 때 코드 수정이 적다.
- 동적 바인딩: 호출 시점에 객체의 실제 타입에 맞는 메소드가 실행되므로, 코드의 활용도가 높아진다
(단점)
- 타입 안전성 저하: 런타임에 타입이 결정되기 때문에, 잘못된 타입의 객체가 할당될 경우 타입 안전성이 떨어질 수 있다. 이 경우 예기치 않은 오류가 발생할 수 있다.
- 가독성 저하: 코드가 길어지면 어떤 객체가 현재 할당되었는지 쉽게 파악하기 어려울 수 있다.
3) animalSound라는 메소드를 정의해, 이 메소드를 통해 Dog와 Cat 객체의 소리를 출력
(장점)
- 높은 재사용성: animalSound() 메소드를 재사용함으로써 코드의 중복을 줄일 수 있다. 새로운 동물 클래스를 추가하더라도 기존 메소드를 그대로 사용할 수 있다.
- 유연한 확장성: 새로운 동물 클래스의 추가가 용이하며, 코드 변경 없이도 다양한 동물의 소리를 출력할 수 있는 구조다.
- Encapsulation: 각 동물의 소리는 클래스 내에서 캡슐화되어 있어, 외부에서 소리 출력 방식에 대해 알 필요가 없다.
(단점)
- 메소드 구현 필요: 각 동물 클래스에서 sound() 메소드를 구현해야 하므로, 모든 자식 클래스에서 메소드 오버라이드가 필요하다. 다양한 클래스가 많아질 경우 관리가 다소 복잡해질 수 있다.
- 추상 클래스 필요: 부모 클래스인 Animal과 같은 추상 클래스나 인터페이스의 정의가 필요하여, 초기 설계가 복잡할 수 있다.
[참고]
혼자 공부하는 자바
다음 내용
[java] instanceof, 인터페이스, 구현 클래스
이전 내용 [java] 추상 클래스, 추상 메소드이전 내용 [java] 클래스의 타입 변환, 다형성이전 내용 [java] 접근 제한자, 클래스 상속이전 내용 [java] 정적 멤버와 Static, 싱글톤이전 내용 [java] java
puppy-foot-it.tistory.com
'프로그래밍 및 기타 > Java, Spring Boot' 카테고리의 다른 글
[java] 인터페이스 상속, 중첩 클래스, 중첩 인터페이스 (0) | 2025.04.08 |
---|---|
[java] instanceof, 인터페이스, 구현 클래스 (0) | 2025.04.08 |
[java] 클래스의 타입 변환, 다형성 (0) | 2025.04.07 |
[java] 접근 제한자, 클래스 상속 (0) | 2025.04.04 |
[java] 정적 멤버와 Static, 싱글톤 (0) | 2025.04.04 |