다형성
다형적 참조: 하나의 변수 타입으로 다양한 자식 인스턴스를 참조할 수 있는 기능
메서드 오버라이딩: 기존 기능을 하위 타입에서 새로운 기능으로 재정의, 무조건 우선권 가짐.





다운캐스팅이 불가능한 경우

업캐스팅이 안전하고 다운캐스팅이 위험한 이유 업캐스팅의 경우 이런 문제가 절대로 발생하지 않는다. 왜냐하면 객체를 생성하면 해당 타입의 상위 부모 타입은 모두 함께 생성된다! 따라서 위로만 타입을 변경하는 업캐스팅은 메모리 상에 인스턴스가 모두 존재하기 때문에 항상 안전하다. 따라서 캐스팅을 생략할 수 있다. 반면에 다운캐스팅의 경우 인스턴스에 존재하지 않는 하위 타입으로 캐스팅하는 문제가 발생할 수 있다. 왜냐하면 객체를 생성하면 부모 타입은 모두 함께 생성되지만 자식 타입은 생성되지 않는다. 따라서 개발자가 이런 문제를 인지하고 사용해야 한다는 의미로 명시적으로 캐스팅을 해주어야 한다.

다운 캐스팅




package poly.basic;
public class CastingMain5 {
public static void main(String[] args) {
Parent parent1 = new Parent();
System.out.println("parent1 호출");
call(parent1);
Parent parent2 = new Child();
System.out.println("parent2 호출");
call(parent2);
}
private static void call(Parent parent) {
parent.parentMethod();
if (parent instanceof Child) {
System.out.println("Child 인스턴스 맞음");
Child child = (Child) parent;
child.childMethod();
}
}
}
실행결과
parent1 호출 Parent.parentMethod parent2 호출 Parent.parentMethod Child 인스턴스 맞음 Child.childMethod

abstract class AbstractAnimal {...}
추상 클래스 설계 이유
동물과 같이 부모 클래스는 제공하지만 , 실제 생성되면 안되는 클래스를 추상 클래스라 한다. 추상 메서드가 하나라도 있는 클래스는 추상 클래스로 선언해야 한다. 추상 메서드는 자식 클래스가 반드시 오버라이딩 되야 하기 때문에 메서드 바디 부분이 없다.
정리
추상 클래스 덕분에 실수로 Animal 인스턴스를 생성할 문제를 근본적으로 방지해준다.
추상 메서드 덕분에 새로운 동물이 자식 클래스를 만들 때 실수로 sound를 오버라이딩 하지 않을 문제를 근본적으로 방지해준다.
순수 추상 클래스 특징
인스턴스를 생성할 수 없다.
상속시 자식은 모든 메서드를 오버라이딩 해야 한다.
주로 다형성을 위해 사용된다.
자바는 순수 추상 클래스를 더 편리하게 사용하도록 인터페이스 개념을 제공한다. 순수 추상 클래스의 특징 때문에 다이아몬드 문제가 발생하지 않는다. 인터페이스에는메서드 구현체가 없기 때문이다.
public abstract class AbstractAnimal {
public abstract void sound();
public abstract void move();
}
public interface InterfaceAnimal {
public abstract void sound();
public abstract void move();
}
인터페이스의 메서드는 모두 public abstract이다
인터페이스 메서드에 public abstract를 생략할 수 있다. 참고로 생략이 권장된다.
인터페이스는 다중 구현(다중 상속)을 지원한다.
인터페이스의 멤버 변수는 public static final이 모두 포함되었다고 간주한다.



1. Spring JPA — 추상 클래스(BaseEntity) 필드 재사용
**공통 상태(필드)**와 기본 동작을 한 번에 묶어 재사용하는 패턴
import jakarta.persistence.*;
import java.time.LocalDateTime;
@MappedSuperclass // JPA: 상속받는 엔티티에 매핑 정보 전달
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
@Column(updatable = false)
protected LocalDateTime createdAt;
@Column
protected LocalDateTime updatedAt;
@PrePersist
public void onCreate() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
public void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
public Long getId() { return id; }
public LocalDateTime getCreatedAt() { return createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
}
@Entity
public class User extends BaseEntity {
private String username;
private String email;
}
특징
모든 엔티티에
id
,createdAt
,updatedAt
자동 포함공통 로직(
@PrePersist
,@PreUpdate
)도 상속받음상태 + 행위를 한 번에 재사용 → 추상 클래스가 적합
2. API 응답 타입 — 인터페이스 상수 사용
규약(값)만 고정해서 여러 클래스에서 동일하게 사용
public interface ResponseType {
String SUCCESS = "success";
String ERROR = "error";
String WARNING = "warning";
}
public class ApiResponse {
private String status;
private Object data;
public static ApiResponse success(Object data) {
return new ApiResponse(ResponseType.SUCCESS, data);
}
public static ApiResponse error(String message) {
return new ApiResponse(ResponseType.ERROR, message);
}
private ApiResponse(String status, Object data) {
this.status = status;
this.data = data;
}
}
특징
상수값이 바뀌지 않아야 하고, 모든 구현체가 같은 값 사용
상태를 저장하거나 변경하지 않음 → 인터페이스 적합
단, 요즘은
enum
으로 관리하는 걸 더 권장
3. 요약
모든 엔티티에 공통 필드와 로직을 물려주기
추상 클래스
상태 + 행위 재사용
응답 타입처럼 변하지 않는 규약 값
인터페이스(상수) 또는 enum
다중 구현 가능, 상수 공유
Last updated