일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- MongoDB
- 리눅스
- oracle
- Exception
- JPA
- IntelliJ
- Linux
- spring
- analytics4
- mysql
- rsync
- SQL
- PostgreSQL
- git
- 트랜잭션
- group by
- 명령어
- docker
- 티스토리챌린지
- 자바
- 오블완
- 호이스팅
- Javascript
- MariaDB
- 차이점
- java
- 인터페이스
- DBMS
- 추상클래스
- mssql
- Today
- Total
hanker
Java - 상속과 다형성의 활용 본문
자바에서 객체지향 프로그래밍(OOP) 개념에서 상속과 다형성은 매우 중요하다.
이번 글에서 어떤 개념인지, 어떻게 활용하는지에 대해서 알아보자.
1. 추상 클래스와 인터페이스
JAVA - JAVA에서 인터페이스와 추상 클래스의 차이점은 무엇일까?
Java에서 인터페이스와 추상 클래스의 차이점에 대해 알아보자. 두 개념 모두 객체 지향 프로그래밍에서 중요한 역할을 하며, 공통된 특성은 있지만, 서로 다른 사용 목적과 특징을 가지고 있다.
hanke-r.tistory.com
1-1. 추상 클래스(Abstract Class)
- abstract 키워드를 사용해 직접 객체를 생성할 수 없는 클래스
- 일부 메서드는 구현할 수 있고, 일부는 abstract 키워드로 선언해 하위 클래스에서 구현을 강제할 수 있음
실무 활용 방법
- 기본적인 동작을 포함하지만, 일부 기능을 하위 클래스에서 변경해야 할 경우
- 공통 코드가 많고, 일부만 변경되는 경우 적합
abstract class Animal {
String name;
Animal(String name) {
this.name = name;
}
void eat() {
System.out.println(name + " is eating.");
}
abstract void makeSound(); // 하위 클래스에서 반드시 구현해야 함
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println(name + " barks!");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog("Buddy");
myDog.eat(); // 공통 기능 사용 가능
myDog.makeSound(); // 오버라이드된 메서드 실행
}
}
1-2. 인터페이스 (Interface)
- 모든 메서드가 기본적으로 abstract (jdk8 이후 default와 static 메서드 사용 가능)
- 다중 구현이 가능하여 여러 기능을 조합할 때 유리하다.
실무 활용 방법
- 특정 기능을 강제할 때 사용 (Java의 Comparable 인터페이스)
- 다중 상속이 필요한 경우 인터페이스로 해결
// Flyable 인터페이스 선언: 'fly' 메서드를 구현해야 함을 명시
interface Flyable {
void fly();
}
// Swimmable 인터페이스 선언: 'swim' 메서드를 구현해야 함을 명시
interface Swimmable {
void swim();
}
// Bird 클래스 선언: Flyable 인터페이스를 구현하여 'fly' 기능을 제공
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("Bird is flying.");
}
}
// Duck 클래스 선언: Flyable과 Swimmable 인터페이스를 모두 구현하여
// 오리의 'fly'와 'swim' 기능을 제공
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("Duck can fly.");
}
@Override
public void swim() {
System.out.println("Duck can swim.");
}
}
// 메인 클래스: 프로그램 실행 진입점
public class Main {
public static void main(String[] args) {
// Duck 객체 생성
Duck duck = new Duck();
// Duck 객체의 'fly' 메서드 호출
duck.fly();
// Duck 객체의 'swim' 메서드 호출
duck.swim();
}
}
비교 항목추상 클래스인터페이스
비교 항목 | 추상 클래스 | 인터페이스 |
다중 상속 지원 | 단일 상속만 가능 | 여러 인터페이스 구현 가능 |
접근 제한자 | 가능 (private, protected 등) | 불가능 (모든 메서드는 public이어야 함) |
기본 메서드 제공 | 가능 (일반 메서드 포함 가능) | JDK 8부터 default 메서드 사용 가능 |
사용 목적 | 공통 기능 제공 + 일부 구현 강제 | 기능 규약 제공 (다형성 극대화) |
2. 인터페이스의 기본 메서드와 정적 메서드
JDK 8 이후, 인터페이스에 default 메서드와 static 메서드를 추가할 수 있게 되었다.
2-1. default 메서드
- 인터페이스를 구현하는 모든 클래스에서 공통적으로 사용할 수 있는 기본 메서드를 정의할 수 있음
- 기존 구현체의 변경 없이 새로운 기능 추가 가능
// Vehicle 인터페이스 정의
interface Vehicle {
// 추상 메서드 move 선언
void move();
// 디폴트 메서드 stop 정의
default void stop() {
// 디폴트 메서드의 기본 구현: "Stopping the vehicle..." 출력
System.out.println("Stopping the vehicle...");
}
}
// Vehicle 인터페이스를 구현하는 Car 클래스 정의
class Car implements Vehicle {
// move 메서드 구현
@Override
public void move() {
// "Car is moving." 출력
System.out.println("Car is moving.");
}
}
// 메인 클래스 정의
public class Main {
// 메인 메서드: 프로그램의 시작 지점
public static void main(String[] args) {
// Car 클래스의 인스턴스 생성
Car car = new Car();
// car 객체의 move 메서드 호출
car.move();
// car 객체의 stop 메서드 호출 (디폴트 메서드 실행)
car.stop();
}
}
2-2. static 메서드
- 인터페이스에서 직접 호출할 수 있음
- 공통적으로 사용될 유틸리티 성격이 메서드를 제공할 때 사용
// MathUtils 인터페이스 정의
interface MathUtils {
// 두 정수의 합을 반환하는 정적 메서드 add 정의
static int add(int a, int b) {
return a + b;
}
}
// 메인 클래스 정의
public class Main {
public static void main(String[] args) {
// MathUtils 인터페이스의 정적 메서드 add를 호출하여 결과를 result에 저장
int result = MathUtils.add(10, 20);
// result 값을 출력
System.out.println("Result: " + result);
}
}
3. 업캐스팅(Upcasting)과 다운캐스팅(Downcasting)
업캐스팅과 다운캐스팅은 객체 지향 프로그래밍에서 클래스 간의 형 변환을 의미하며, 주로 상속 관계에 있는 클래스들 사이에서 발생한다.
3-1. 업캐스팅 (Upcasting)
- 하위 클래스 객체를 부모 타입(상위 타입)으로 변환
- 다형성을 활용할 때 필수적으로 사용된다.
class Parent {
void show() {
System.out.println("Parent class method");
}
}
class Child extends Parent {
@Override
void show() {
System.out.println("Child class method");
}
}
public class Main {
public static void main(String[] args) {
Parent obj = new Child(); // 업캐스팅
obj.show(); // Child의 오버라이드된 메서드 실행
}
}
3-2. 다운캐스팅(Downcasting)
- 부모 타입을 다시 자식 타입으로 변환
- instanceof 연산자로 타입 체크 후 캐스팅 하는 것이 안전하다.
* instanceof 연산자 : 객체가 특정 클래스, 상속받은 클래스를 확인하기 위해 사용
public class Main {
public static void main(String[] args) {
Parent obj = new Child(); // 업캐스팅
if (obj instanceof Child) {
Child childObj = (Child) obj; // 다운캐스팅
childObj.show();
}
}
}
- 다운캐스팅은 잘못 사용하면 ClassCastException 이 발생하니 반드시 instanceof 연산자를 활용해 확인 후 캐스팅해야한다.
4. 다형성의 활용
4-1. 전략 패턴 (Strategy pattern)
- 다형성을 활용한 대표적인 디자인 패턴
- 런타임에서 동적으로 객체의 행동을 변경 가능
// 결재 방식을 정의하는 인터페이스
interface PaymentStrategy {
// 구현하는 클래스에서 실제 결제 로직을 정의한다.
void pay(int amount);
}
// 전략 1: 신용카드 결제
class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
// 신용카드로 결제하는 방식의 출력문 작성
System.out.println("Paid " + amount + " using Credit Card.");
}
}
// 전략 2: 현금 결제
class CashPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
// 현금으로 결제하는 방식의 출력문 작성
System.out.println("Paid " + amount + " using CashPayment.");
}
}
// 위 전략 2개를 실행하는 컨텍스트
class PaymentContext {
// 결제 전략을 저장할 변수
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
// 실행할 전략을 설정
this.strategy = strategy;
}
public void executePayment(int amount) {
// 현재 설정된 전략의 pay 메서드 실행
strategy.pay(amount);
}
}
public class Main {
public static void main(String[] args) {
PaymentContext context = new PaymentContext(new CreditCardPayment());
context.executePayment(1000);
context = new PaymentContext(new CashPayment());
context.executePayment(2000);
}
}
위 방식은 새로운 결제 방법을 추가해도 기존 코드의 수정 없이 확장이 가능하다 (OCP 원칙 준수)
정리
- 추상 클래스와 인터페이스를 적절히 활용하면 코드의 유지보수성이 높아진다.
- 업캐스팅과 다운캐스팅을 활용해 유연한 설계를 할 수 있다. (나중에 이 내용으로 보다 더 심도있게 다룰 예정)
- 전략 패턴 같은 디자인 패턴을 적용하면 실무에서도 효과적으로 활용 가능하다.
'JAVA' 카테고리의 다른 글
Java - List 인터페이스 (ArrayList, LinkedList, Vector 등) (1) | 2025.02.12 |
---|---|
Java - 예외 처리의 활용 및 효율적인 관리 방법 (Custom Exception, Exception Propagation) (0) | 2025.02.10 |
JAVA - 멀티스레딩 가이드 (구현부터 동기화까지) (0) | 2025.01.07 |
Java - CheckedException과 UncheckedException이 뭐지? (0) | 2024.12.06 |
Java - 컴파일타임(Compile Time)과 런타임(Run Time)이 뭘까? (0) | 2024.12.05 |