변수의 기록

(자바) 자바의 신 day5 - 자바에서 업캐스팅과 다운캐스팅, equals와 hashCode, toString의 역할 및 오버라이딩 이유 본문

자바/자바

(자바) 자바의 신 day5 - 자바에서 업캐스팅과 다운캐스팅, equals와 hashCode, toString의 역할 및 오버라이딩 이유

불광동 물주먹 2025. 6. 25. 06:00

자바에서 업캐스팅과 다운캐스팅, equals와 hashCode, toString의 역할 및 오버라이딩 이유

1. 업캐스팅과 다운캐스팅의 원리와 목적

✅ 업캐스팅 (Upcasting)

  • 자식 객체를 부모 타입으로 참조하는 것
  • 예: Parent p = new Child();
  • 자동 형변환이 일어나며, 부모가 가진 멤버만 접근 가능
  • 런타임 시에는 실제 객체가 Child이더라도, 컴파일러는 Parent 기준으로 접근 허용
  • 다형성의 핵심: 다양한 자식 객체를 하나의 부모 타입으로 묶어 공통된 방식으로 처리 가능

✅ 다운캐스팅 (Downcasting)

  • 부모 타입으로 참조된 객체를 다시 자식 타입으로 변환
  • 예: Child c = (Child) p;
  • 명시적 형변환이 필요하며, 실제 객체가 해당 자식 타입일 때만 안전하게 가능
  • 그렇지 않으면 ClassCastException 발생
  • 안전한 다운캐스팅을 위해 instanceof 사용 권장:
  • if (p instanceof Child) { Child c = (Child) p; }

✅ 왜 굳이 업/다운캐스팅을 쓸까?

  • 다형성을 통해 여러 자식 타입을 하나의 부모 타입 컬렉션(List, Set 등)으로 처리 가능
  • 유지보수성과 확장성을 높이기 위해 인터페이스나 추상 클래스를 기준으로 설계하고 업캐스팅하여 통합 처리함

2. toString() 오버라이딩의 이유

✅ Object 클래스의 toString()

  • 자바의 모든 클래스는 Object 클래스를 상속함 (암묵적으로)
  • Object.toString() 기본 구현은 다음과 같음:
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  • 출력 예시: User@3e3abc88
  • 클래스명과 해시코드를 문자열로 반환하지만, 객체 내용은 보이지 않음

✅ 오버라이딩 이유 (Lombok을 사용하지 않는 경우)

  • 객체의 내부 상태(필드 값 등)를 사람이 읽을 수 있게 표현하기 위함
  • 디버깅, 로그 출력, 테스트 시 가독성을 높임
  • Lombok의 @ToString, @Data는 자동 생성 지원
@Override
public String toString() {
    return "User{name='" + name + "', age=" + age + "}";
}

3. equals() 오버라이딩의 이유

✅ 기본 equals() 동작 (Object)

public boolean equals(Object obj) {
    return this == obj;
}
  • 기본은 주소 비교 → 내용이 같아도 false 나옴

✅ DTO에서 equals() 오버라이딩 이유 (Lombok을 사용하지 않는 경우)

  • DTO는 값 중심 객체이므로, 내부 필드 값이 같으면 같다고 판단해야 함
  • 컬렉션(Set, Map 등)에서의 비교, 중복 제거 시 정확한 동작을 위해 필수
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    UserDTO user = (UserDTO) o;
    return age == user.age && Objects.equals(name, user.name);
}

✅ 단, 대부분의 실무 DTO는 Lombok을 사용하므로 @EqualsAndHashCode 또는 @Data 어노테이션으로 자동 생성하는 것이 일반적이다.


4. hashCode() 오버라이딩의 이유

✅ Object 클래스의 hashCode()

  • 기본 구현은 객체의 메모리 주소 기반 정보를 정수값으로 변환한 해시값을 반환함
  • 직접 메모리 주소는 아니며, 객체의 ID나 JVM 내부 알고리즘에 의해 계산된 숫자
  • 기본 구현 예시:
public native int hashCode();

✅ 왜 오버라이딩해야 하나? (Lombok을 사용하지 않는 경우)

  • HashSet, HashMap, Hashtable 등 해시 기반 컬렉션에서 객체 비교 시 hashCode()로 먼저 비교 후 equals() 수행
  • 값이 같으면 동일한 해시코드가 나와야 함 → equals와 hashCode는 항상 쌍으로 오버라이딩
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

✅ 자바의 규약

  • equals() == true 이면 hashCode()도 같아야 함
  • hashCode()만 같고 equals()는 다를 수 있음 (충돌은 허용됨)

5. Lombok이 자동으로 해주는 것

어노테이션 toString() equals/hashCode() getter/setter

@ToString
@EqualsAndHashCode
@Data
@Value (불변 DTO) ✅ (final)

6. 실무 설계 팁 및 확장 내용

  • 다운캐스팅이 자주 보인다면 설계를 재검토해야 함 (인터페이스나 추상 메서드로 해결 가능)
  • equals/hashCode 없이 HashSet 등에 DTO를 넣으면 중복 데이터 비교가 불가능해짐
  • record(Java 14+)를 사용하면 toString(), equals(), hashCode() 자동 생성됨
public record UserDTO(String name, int age) {}

7. 결론 정리

  • 업캐스팅은 다형성을 위해, 다운캐스팅은 자식 고유 기능을 쓸 때만 제한적으로 사용
  • **toString()**은 객체를 사람이 보기 쉽게, **equals/hashCode()**는 값 비교를 위해 필수
  • Lombok이나 record를 활용하면 코드량은 줄고 명확한 객체 정의 가능
  • 실무에서는 equals/hashCode 오버라이딩 여부에 따라 동작이 완전히 달라지므로 반드시 주의할 것