본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
Ch 7. 상속과 다형성
자바 객체 지향 프로그래밍의 꽃: 상속과 다형성으로 견고한 시스템 만들기
자바 객체 지향 프로그래밍(OOP)의 진정한 강력함을 보여주는 상속(Inheritance)과 다형성(Polymorphism)에 대해 자세히 알아보겠습니다. 이 두 가지 개념은 코드의 재사용성을 극대화하고 유연하며 확장 가능한 시스템을 구축하는 데 필수적입니다.
1. 상속(Inheritance): 코드 재사용의 기본
상속은 객체 지향 프로그래밍에서 한 객체 또는 클래스가 다른 객체 또는 클래스를 기반으로 하여 유사한 구현을 유지하는 메커니즘입니다. 이는 부모-자식 관계를 형성하여 코드를 재사용하고 확장하는 데 도움을 줍니다. 자바에서는 extends 키워드를 사용하여 상속을 구현합니다.
상속의 이점:
- 코드 재사용: 부모 클래스에 정의된 필드(멤버 변수)와 메소드를 자식 클래스에서 다시 작성할 필요 없이 사용할 수 있습니다.
- 유지보수 용이성: 공통된 로직이 부모 클래스에 집중되므로, 변경이 필요할 때 한 곳만 수정하면 됩니다.
- 확장성: 기존 클래스를 수정하지 않고도 새로운 기능을 추가하여 시스템을 확장할 수 있습니다.
1.1. extends 키워드와 클래스 계층
예를 들어, 다양한 종류의 '상품'을 관리해야 한다고 가정해 봅시다. 모든 상품은 '이름'과 '가격'이라는 공통 속성을 가질 수 있지만, 가격이 고정된 상품(FixedPricedProduct)과 매일 가격이 변동하는 상품(DailyPricedProduct)처럼 다른 동작을 가질 수 있습니다. 이럴 때 상속을 통해 Product라는 일반적인 부모 클래스를 만들고, 이를 확장하여 특정 상품 클래스를 만들 수 있습니다.
// Product 클래스 (부모 클래스)
public class Product {
private String name;
protected int price; // protected는 상속 관계에서 자식 클래스가 접근 가능하게 함
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return this.name;
}
public int getPrice() { // 일반 상품의 가격을 반환하는 메소드
return this.price;
}
}
// FixedPricedProduct 클래스 (Product를 상속받음)
class FixedPricedProduct extends Product { //
public FixedPricedProduct(String name, int price) {
super(name, price); // 부모 클래스(Product)의 생성자 호출
}
// 고정 가격 상품은 부모의 getPrice()를 그대로 사용하거나 필요시 오버라이딩 가능
}
// DailyPricedProduct 클래스 (Product를 상속받음)
class DailyPricedProduct extends Product {
private boolean nearTheDeadline; // 마감 임박 여부
public DailyPricedProduct(String name, int price) {
super(name, price);
this.nearTheDeadline = false; // 기본값
}
public DailyPricedProduct(String name, int price, boolean nearTheDeadline) {
super(name, price);
this.nearTheDeadline = nearTheDeadline;
}
public void setNearTheDeadline(boolean nearTheDeadline) {
this.nearTheDeadline = nearTheDeadline;
}
@Override //
public int getPrice() { // DailyPricedProduct는 가격 할인 로직을 가짐
if (nearTheDeadline) {
return (int) (this.price * 0.5); // 마감 임박 상품은 50% 할인
}
return this.price;
}
}
public class InheritanceExample {
public static void main(String[] args) {
FixedPricedProduct book = new FixedPricedProduct("켄트 벡의 Tidy First?", 19800);
DailyPricedProduct tteokbokki = new DailyPricedProduct("맛있는 떡볶이", 6000, true);
DailyPricedProduct apple = new DailyPricedProduct("영주 사과", 2000, false);
System.out.println("상품명: " + book.getName() + ", 가격: " + book.getPrice() + "원");
System.out.println("상품명: " + tteokbokki.getName() + ", 가격: " + tteokbokki.getPrice() + "원 (할인 적용)");
System.out.println("상품명: " + apple.getName() + ", 가격: " + apple.getPrice() + "원");
}
}
1.2. 추상 메소드(Abstract Method)와 추상 클래스(Abstract Class)
어떤 경우에는 부모 클래스에서 특정 메소드의 구현은 없고 서명(signature)만 정의하고 싶을 수 있습니다. 이때 추상 메소드를 사용합니다. 추상 메소드는 바디(메소드 본문)가 없는 메소드이며, 이를 포함하는 클래스는 반드시 추상 클래스로 선언되어야 합니다.
추상 클래스는 new 키워드와 생성자를 통해 직접 객체를 생성할 수 없습니다. 대신, 추상 클래스를 상속받는 하위 클래스가 추상 메소드를 반드시 구현해야 합니다. 이는 설계의 일관성을 강제하고, 모든 자식 클래스가 특정 기능을 가지도록 보장하는 강력한 메커니즘입니다.
// 추상 Product 클래스
public abstract class Product { //
private String name;
protected int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return this.name;
}
public abstract int getPrice(); // 추상 메소드: 구현부가 없음 [cite: 155, 157]
}
// FixedPricedProduct는 Product의 getPrice()를 구현해야 함
class FixedPricedProduct extends Product {
public FixedPricedProduct(String name, int price) {
super(name, price);
}
@Override
public int getPrice() { // 추상 메소드 구현
return this.price;
}
}
// DailyPricedProduct는 Product의 getPrice()를 구현해야 함
class DailyPricedProduct extends Product {
private boolean nearTheDeadline;
public DailyPricedProduct(String name, int price) {
super(name, price);
this.nearTheDeadline = false;
}
public DailyPricedProduct(String name, int price, boolean nearTheDeadline) {
super(name, price);
this.nearTheDeadline = nearTheDeadline;
}
public void setNearTheDeadline(boolean nearTheDeadline) {
this.nearTheDeadline = nearTheDeadline;
}
@Override
public int getPrice() { // 추상 메소드 구현
if (nearTheDeadline) {
return (int) (this.price * 0.5);
}
return this.price;
}
}
public class AbstractClassExample {
public static void main(String[] args) {
// Product product = new Product("테스트", 1000); // 오류: 추상 클래스는 객체 생성 불가
Product book = new FixedPricedProduct("자바 프로그래밍의 정석", 35000);
Product milk = new DailyPricedProduct("신선한 우유", 3000, true);
System.out.println("상품명: " + book.getName() + ", 가격: " + book.getPrice() + "원");
System.out.println("상품명: " + milk.getName() + ", 가격: " + milk.getPrice() + "원");
}
}
2. 다형성(Polymorphism): 하나의 인터페이스, 여러 형태
다형성은 프로그래밍 언어 이론 및 타입 이론에서 단일 기호를 사용하여 여러 다른 타입을 나타내는 것을 의미합니다. 객체 지향 프로그래밍에서 다형성은 다른 타입의 엔티티에 단일 인터페이스를 제공하는 것입니다. 이는 동일한 메소드 호출이 객체의 실제 타입에 따라 다른 동작을 수행하도록 하는 강력한 기능입니다.
2.1. 다형성의 주요 구현 방안
- 메소드 오버로딩 (Method Overloading): 같은 이름의 메소드를 여러 개 만들되, 매개변수의 수, 타입, 순서가 다르게 하여 구분하는 방식입니다.
- 예시: System.out.println() 메소드는 문자열, 정수 등 다양한 타입의 인수를 받을 수 있습니다.
public class OverloadingExample { public void print(int num) { System.out.println("정수: " + num); } public void print(String text) { // 같은 이름이지만 매개변수 타입이 다름 System.out.println("문자열: " + text); } public void print(int num, String text) { // 매개변수 개수가 다름 System.out.println("정수: " + num + ", 문자열: " + text); } public static void main(String[] args) { OverloadingExample oe = new OverloadingExample(); oe.print(10); oe.print("Hello"); oe.print(20, "World"); } }
- 매개변수 다형성 (Parametric Polymorphism / Generics): 함수나 데이터 타입을 제네릭하게 작성하여, 타입에 의존하지 않고 값을 균일하게 처리할 수 있도록 하는 기능입니다. 자바의 제네릭스(Generics)는 컴파일 시점에 타입 안전성을 제공하면서도 다양한 타입의 객체에 대해 작동하도록 설계되었습니다.
- 예시: List<String>과 같이 컬렉션에 저장될 타입을 명시하여 런타임 오류를 컴파일 시점에 감지할 수 있게 합니다.
import java.util.ArrayList; import java.util.List; public class GenericsExample { public static void main(String[] args) { // 제네릭스를 사용하지 않은 경우 (런타임 오류 가능성) List nonGenericList = new ArrayList(); nonGenericList.add("Hello"); // final Integer i = (Integer) nonGenericList.get(0); // 런타임 ClassCastException 발생 [cite: 228, 229, 230] // 제네릭스를 사용한 경우 (컴파일 시점 타입 안전성) List<String> genericList = new ArrayList<>(); // String 타입만 저장 가능 genericList.add("World"); // final Integer i = (Integer) genericList.get(0); // 컴파일 오류 발생 (타입 불일치) String s = genericList.get(0); System.out.println(s); } }
- 서브타이핑 (Subtyping) / 메소드 오버라이딩: 상속 관계에서 부모 타입의 참조 변수가 자식 타입의 객체를 가리킬 수 있게 하는 것입니다. 이를 통해 부모 클래스의 메소드를 자식 클래스에서 재정의(오버라이딩)하여, 동일한 메소드 호출이라도 객체의 실제 타입에 따라 다른 동작을 수행하게 합니다.
// Product, FixedPricedProduct, DailyPricedProduct 클래스는 위에서 정의된 것을 사용 public class SubtypingExample { public static void main(String[] args) { // 부모 타입인 Product로 자식 객체들을 참조 Product product1 = new FixedPricedProduct("연필", 500); // [cite: 148] Product product2 = new DailyPricedProduct("유기농 채소", 12000, true); // [cite: 149] Product product3 = new DailyPricedProduct("수입 과일", 8000, false); // [cite: 150] // 동일한 getPrice() 메소드를 호출하지만, 실제 객체의 타입에 따라 다른 동작을 함 System.out.println(product1.getName() + "의 가격: " + product1.getPrice() + "원"); System.out.println(product2.getName() + "의 가격: " + product2.getPrice() + "원"); System.out.println(product3.getName() + "의 가격: " + product3.getPrice() + "원"); // 주문 목록을 Product 타입으로 관리하여 다형성을 활용 Product[] orderList = new Product[3]; orderList[0] = product1; orderList[1] = product2; orderList[2] = product3; int totalOrderPrice = 0; for (Product p : orderList) { totalOrderPrice += p.getPrice(); // 각 객체의 오버라이딩된 getPrice()가 호출됨 } System.out.println("\n총 주문 가격: " + totalOrderPrice + "원"); } }
3. Java 도큐먼트에서 찾을 수 있는 정보들 📚
자바 공식 도큐먼트는 자바 개발자에게 가장 중요한 자료 중 하나입니다. 특히, Java API 문서(https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Object.html)는 모든 클래스와 메소드에 대한 상세 정보를 제공합니다.
- java.lang.Object 클래스: 이 클래스는 자바 클래스 계층 구조의 루트(root)입니다. 모든 클래스는 Object를 슈퍼클래스로 가지며, 배열을 포함한 모든 객체는 이 클래스의 메소드를 구현합니다.toString(), equals(), hashCode() 등 기본적으로 중요한 메소드들이 여기에 정의되어 있습니다.
- 생성자: 자바 생성자는 객체를 생성하기 위해 호출되는 특별한 타입의 함수입니다. 이는 새 객체를 사용할 준비를 하고, 필요한 멤버 변수를 설정하기 위한 인수를 종종 받습니다. 생성자에는 매개변수가 있는 생성자, 매개변수가 없는 생성자, 그리고 기본 생성자(디폴트 생성자)가 있습니다.
- 접근 제어자 (Access Levels): public, protected, default, private와 같은 키워드를 사용하여 클래스, 메소드, 변수 등에 대한 접근 수준을 제어합니다. 이는 캡슐화 원칙을 구현하는 데 중요하며, 코드의 안정성과 일관성을 높입니다.
오늘의 실습
오늘의 느낀점
객체지향언어에서, 자바에서 상속과 다형성은 꽤 중요한 개념 중 하나이고 실제로 자바의 소스가 구현되어 있는 것을 보아도 어렵지 않게 찾아 볼 수 있다. 설계단계에서부터 상속을 염두해두고 코드를 구성해 나가면 소스코드가 훨씬 깔끔해지고 포함관계가 명확해지니 설계부터 깔끔하게 가야겠다. 언젠가 다시 자바로 실무를 하는 날이 올테니 꼭 지금 공부할 때 개념 다시 잘 챙겨두고 정리해야겠다!
https://fastcampus.info/4n8ztzq
(~6/20) 50일의 기적 AI 환급반💫 | 패스트캠퍼스
초간단 미션! 하루 20분 공부하고 수강료 전액 환급에 AI 스킬 장착까지!
fastcampus.co.kr
'개발공부 > Java' 카테고리의 다른 글
패스트캠퍼스 환급챌린지 24일차 : 한 번에 끝내는 컴퓨터 공학 & 인공지능 복수전공 초격차 패키지 강의 후기 (1) | 2025.07.24 |
---|---|
패스트캠퍼스 환급챌린지 23일차 : 한 번에 끝내는 컴퓨터 공학 & 인공지능 복수전공 초격차 패키지 강의 후기 (3) | 2025.07.23 |
패스트캠퍼스 환급챌린지 21일차 : 한 번에 끝내는 컴퓨터 공학 & 인공지능 복수전공 초격차 패키지 강의 후기 (2) | 2025.07.21 |
패스트캠퍼스 환급챌린지 20일차 : 한 번에 끝내는 컴퓨터 공학 & 인공지능 복수전공 초격차 패키지 강의 후기 (0) | 2025.07.20 |
패스트캠퍼스 환급챌린지 19일차 : 한 번에 끝내는 컴퓨터 공학 & 인공지능 복수전공 초격차 패키지 강의 후기 (0) | 2025.07.19 |