본문 바로가기
Books/Effective Java

3장. 모든 객체의 공통 메서드

by YoonJong 2024. 4. 3.
728x90

 

아이템10. equals는 일반 규약을 지켜 재정의하라.

  • 언제 사용할까 ?
    1. 논리적 동치성을 확인해야 할 때
    2. 값 클래스(String, Integer 등) 등 객체가 같은지가 아니라 값이 같은지 알고 싶을 때
  • 다음 상황 중 하나에 해당하면 재정의 하지 않는 것이 최선.
    1. 각 인스턴스가 본질적으로 고유하다.
    2. 인스턴스의 ‘논리적 동치성’ 을 검사할 일이 없다.
    3. 상위 클래스에서 재정의한 equals 가 하위 클래스에도 딱 들어맞는다.
    4. 클래스가 private 이거나 package-private 이고 equals 메서드를 호출할 일이 없다.
  • 오버라이딩 시 고려 사항:
    • 비교 대상 필드 모두 비교
    • null 체크
    • 반사성(reflexivity): 자신과 자신을 비교했을 때 true 반환
    • 대칭성(symmetry): a.equals(b) == b.equals(a)
    • 전이성(transitivity): a.equals(b) && b.equals(c) == a.equals(c)
  • equals() 메소드 재정의 예시
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // equals() 메소드 재정의
    @Override
    public boolean equals(Object obj) {
		    // 자기 자신 참조인지 확인
        if (obj == this) {
            return true;
        }
				// 올바른 타입인지 확인
        if (!(obj instanceof Person)) {
            return false;
        }

        Person other = (Person) obj;

        return name.equals(other.name) && age == other.age;
    }
}
  • 동일성
    • 정의: 두 객체가 메모리 상에서 같은 주소를 가리키는지 비교
    • 비교 연산자: ==
String str1 = "Hello";
String str2 = "Hello";

System.out.println(str1 == str2); // true (메모리 상 같은 주소)
  • 동등성
    • 정의: 두 객체의 값이 논리적으로 동일한지 비교
    • 비교 메소드: equals()
String str1 = new String("Hello");
String str2 = new String("Hello");

System.out.println(str1 == str2); // false (메모리 상 다른 주소)
System.out.println(str1.equals(str2)); // true (값은 동일)
  • 동치성
    • 정의: 동일성과 동등성을 모두 만족하는 경우
    • 사용 상황: 동일성과 동등성을 모두 구분할 필요가 없는 경우

아이템11. equals를 재정의하려거든 hashcode도 재정의하라.

! 읽기전 참고

<aside> 💡 동일성

  • 정의: 두 객체가 메모리 상에서 같은 주소를 가리키는지 비교
  • 비교 연산자: ==

동등성

  • 정의: 두 객체의 값이 논리적으로 동일한지 비교
  • 비교 메소드: equals()

동치성

  • 정의: 동일성과 동등성을 모두 만족하는 경우
  • 사용 상황: 동일성과 동등성을 모두 구분할 필요가 없는 경우 </aside>
  • equals를 재정의한 클래스 모두에서 hashcode도 재정의해야 한다.
    • equals() 는 두 객체의 내용이 동일한지 비교한다.
    • hashCode() 는 객체를 해시 코드로 변환하여 해시 테이블과 같은 자료 구조에서 빠르게 검색할 수 있도록 한다.
    • equals()가 두 객체를 동일하다고 판단하면 두 객체의 hashCode() 값도 같아야 합니다.
    • 반대로, 두 객체의 hashCode() 값이 다르면 equals()는 두 객체를 동일하다고 판단할 수 없습니다.
  • Java에서 equals()를 재정의할 때 hashCode()도 같이 재정의하는 것은 객체의 동등성을 정확하게 비교하고 해시 테이블과 같은 자료 구조에서 올바르게 작동하도록 하기 위해 필수적인 작업입니다. 또한, 비교 성능을 향상시키는 효과도 있다.

  • IDE(인텔리제이)에서 기본으로 제공하는 기능을 사용하는 것이 좋다 → 수동으로 equals 와 hashcode를 재정의하는 것은 쉽지 않다.
public class Person {

    private String name;
    private int age;

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof Person) {
            Person other = (Person) obj;
            return Objects.equals(name, other.name) && age == other.age;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

}

 


아이템12. toString을 항상 재정의하라.

toString 메소드는 객체를 사람이 읽을 수 있는 문자열로 반환하는 기능을 한다.

모든 객체에는 이 toString 메소드가 기본적으로 존재하며, 기본 구현은 이 객체가 어떤 클래스의 인스턴스인지와, 그 인스턴스의 해시코드 값을 반환한다.

예시 : java.lang.Object@5b97f321

그러나 이런 기본 반환값은 일반적으로 유용한 정보를 제공하지 않는다.

따라서 toString 메소드는 해당 클래스의 인스턴스에 대한 유용한 정보를 반환하도록 재정의하는 것이 좋다.

 

toString의 재정의 또한 IDE 에서 기본으로 제공해주는 기능을 보통 사용하며, 기호에 따라 직접 재정의한다.

@Override
  public String toString() {
      return "Rectangle{" +
              "width=" + width +
              ", height=" + height +
              '}';
  }

 


아이템13. clone 재정의는 주의해서 진행하라.

아무런 메서드가 없다.

Cloneable 인터페이스는 객체를 복제할 수 있다는 것을 나타내는 마커 인터페이스이다.

즉, clone() 메서드를 구현하여 객체의 필드별 복사본을 만들 수 있다는 것을 의미한다.

clone() 메서드는 java.lang.Object 클래스에서 정의된 protected 메서드이며, 다음과 같은 특징을 가진다.

  • 얕은 복제: 객체의 필드 값만 복사한다. 참조 필드가 가리키는 객체는 복제되지 않는다.
  • protected 접근 제어자: 직접 호출할 수 없으며, 상속받은 클래스에서만 호출 가능하다.
  • CloneNotSupportedException: 클래스가 Cloneable 인터페이스를 구현하지 않으면 예외를 발생시킨다.
  • 배열은 clone 메서드 방식이 가장 깔끔한 방법이다.
    • clone() 메서드는 배열의 모든 요소를 복제하여 새 배열을 만들고, 새 배열의 주소를 반환한다.
int[] copiedNumbers = (int[]) numbers.clone(); // 배열의 내용만 복사 ( 얕은 복제 )

System.out.println("numbers 배열 주소값: " + System.identityHashCode(numbers));
System.out.println("copiedNumbers 배열 주소값: " + System.identityHashCode(copiedNumbers));

System.out.println(Arrays.toString(numbers)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(copiedNumbers)); // [1, 2, 3, 4, 5]

// 출력 결과
numbers 배열 주소값: 123456789
copiedNumbers 배열 주소값: 987654321
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

++ 얕은 복제 vs. 깊은 복제

  • 배열의 요소가 객체인 경우, 얕은 복제와 깊은 복제의 차이점을 고려해야 한다.
  • 얕은 복제는 요소를 참조하는 주소만 복제하는 반면, 깊은 복제는 요소 자체를 복제한다.

아이템14. Comparable을 구현할지 고려하라.

  • 순서를 고려해야 하는 값 클래스를 작성한다면 꼭 Comparable 인터페이스를 구현해서, 쉽게 정렬하고 검색하고 비교 기능을 제공하는 컬렉션과 같이 사용한다.
  • compartTo 는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며 제네릭하다.
  • Comparable 을 구현한 객체들의 배열은 아래와 같은 방법으로 쉽게 정렬 가능하다.
Arrays.sort(a);

compareTo(p1, p2) 리턴값 비교 분석

Comparable 인터페이스에서 compareTo() 메서드는 두 객체를 비교하여 정수 값을 반환한다.

반환값에 따라 객체의 순서가 결정된다.

1. 리턴값이 양수일 때:

  • p1이 p2보다 "크다" 라는 의미
  • 정렬 시 p1은 p2보다 뒤쪽에 위치
  • 구체적인 예시:
    • 문자열 비교: p1의 문자열이 사전 순서에서 p2의 문자열보다 뒤쪽에 오는 경우
    • 숫자 비교: p1의 숫자가 p2의 숫자보다 큰 경우
    • 날짜 비교: p1의 날짜가 p2의 날짜보다 늦은 경우

2. 리턴값이 0일 때:

  • p1과 p2가 "같다" 라는 의미
  • 정렬 시 p1과 p2는 같은 위치에 유지
  • 구체적인 예시:
    • 문자열 비교: p1과 p2의 문자열이 동일한 경우
    • 숫자 비교: p1과 p2의 숫자가 동일한 경우
    • 날짜 비교: p1과 p2의 날짜가 동일한 경우

3. 리턴값이 음수일 때:

  • p1이 p2보다 "작다" 라는 의미
  • 정렬 시 p1은 p2보다 앞쪽에 위치
  • 구체적인 예시:
    • 문자열 비교: p1의 문자열이 사전 순서에서 p2의 문자열보다 앞쪽에 오는 경우
    • 숫자 비교: p1의 숫자가 p2의 숫자보다 작은 경우
    • 날짜 비교: p1의 날짜가 p2의 날짜보다 빠른 경우
728x90

'Books > Effective Java' 카테고리의 다른 글

2장. 객체 생성과 파괴  (0) 2024.03.30

댓글