3 minute read

Effective Java Item 10을 보다 헷갈려 적으며 보려 포스팅

1. equals 메서드?

  • equals는 Object 클래스에 정의된 ‘메서드’ 이며 String, Integer, Double 등의 하위 클래스에서 오버라이딩 되어 있다.
  • 위의 오버라이이 된다는 점에서 == 와는 차이를 보인다.
  • equals 메서드는 객체 내의 정보들에 동등성 비교를 목적으로 하는 메서드 이다.
  • equals 메서드를 잘못 작성하게 되면 의도하지 않는 결과를 초래 하므로 Effective Java에선 변경하지 않고 쓰는걸 권장한다.

※ equals and hashcode

equals() : 두 객체의 내용이 같은지를 확인하는 메서드. hashCode() : 두 객체가 같은 객체인지를 확인하는 메서드.

※ equals and hashcode example

public class Student {
    private int grade;
    private int age;
    private String name;

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

public class Main {
  public static void main(String[] args) {
    Student student1 = new Student(6,13,"철수");
    Student student2 = new Student(6,13,"철수");

    System.out.println(student1.equals(student2)); // 결과는 false 
  }
}

위의 결과는 false가 나온다 왜냐면 Object의 equals 메서드는 기본적으로 == 주소 비교를 하기 때문이다. 이걸 수정하기 위해선 Override 하여 사용 하여야 한다. 이건 조금 조심하여야 하는데 이유는 아래 2번 글부터 확인이 가능하니 천천히 확인하면 될것 같다.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Student)) return false;
    Student student = (Student) o;
    return grade == student.grade && age == student.age && name.equals(student.name);
}

요걸 추가하면 된다. 난 그냥 IDE 기본 설정을 사용

그런데 난 사실 equals 를 Map key를 사용할때 많이 사용한다.

public class Main {
    public static void main(String[] args) {
        Student student1 = new Student(6,13,"철수");
        Student student2 = new Student(6,13,"철수");

        Map<Student, Integer> map = new HashMap<>();
        map.put(student1,1);
        map.put(student2,1);
        System.out.println(map.size()); //!! key값이 같은데 2개가 들어간다??? 무슨일인가?
        System.out.println(map); //{com.company.Student@4554617c=1, com.company.Student@1b6d3586=1} 다른 객체를 갖는걸 확인이 가능하다.
    }
}

이를 해결하기 위해 Student class에 아래 코드를 추가해준다.

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

이 equals 메서드와 hashCode 메서드는 Override 할때 같이 재정의 해주는 것이 좋다. 이건 Effective Java에도 나와있으며 아래 포스팅과 다음 포스팅에서 다루도록 하겠다. :0


아래 부턴 Effective Java 의 내용을 다룬다.

2. equals 메서드를 재정의 하지 않아도 되는 경우

  • 각 인스턴스가 본질적으로 고유한경우
    • 주로 값을 표현하는게 아니라 동작하는 것을 표현하는 클래스, Thread가 좋은 예, Object의 equals 메서드는 이러한 클래스에 딱 맞게 구현되어 있음
  • 인스턴스의 논리적 동치성 (Logicla Equality)를 검사할 일이 없는 경우
  • 상위 클래스에서 재정의한 equals가 하위 클래스에서도 적용 되는 경우
  • 클래스가 private 이거나, package-private 여서 equals를 호출할 일이 없는 경우
  • 싱글턴을 보장하는 클래스 (인스턴스 통제 클래스, Enum)인 경우

3. equals 메서드를 재정의 해야 할 때

  • equals 메소드는 ‘메모리주소를 기반으로 물리적으로 같은지 여부’ 즉 객체의 식별성이 아니라 논리적 동치성을 비교할때 재정의 하면 좋다. 주로 값 클래스들에 해당된다. 다시말하면 객체가 같은지가 아닌 객때 재정의 객체내 값이 같은지를 비교할 때 재정의 해야 한다. ※ Map의 Key, Set의 원소 등으로 사용하려면 재정의 필요

4. equals 재정의 할 경우 지켜야 하는 규약

  1. 반사성
  2. 대칭성
  3. 추이성
  4. 일관성

4-1 반사성?

null 이 아닌 모든 참조 값 x에 대해 x.equals(x)를 만족
객체는 자기 자신과 비교했을때 같아야 한다. 당연한 케이스 

4-2 대칭성 (symmetry)

null이 아닌 모든 참조 값 x,y에 대해 x.equals(y) true이면, y.equals(x) 또한 true를 만족해야 한다.


import java.util.Objects;

public class CaseInsensitiveString {
  private final String s;

  public CaseInsensitiveString(String s){
    this.s = Objects.requireNonNull(s);
  }

  //equalsIgnoreCase은 대소문자 구별없이 문자열 equals 검사
  @Override public boolean equals(Object o) {
    if (o instanceof CaseInsensitiveString)
      return s.equalsIgnoreCase(
              ((CaseInsensitiveString) o).s);

    if(o instanceof String)
      return s.equalsIgnoreCase((String) o);

    return false;
  }

}

public class Main {

    public static void main(String[] args) {
        CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
        String s = "polish";

        System.out.println(cis.equals(s)); // true
        System.out.println(s.equals(cis)); // false String의 equals 메서드는 CaseInsentiveSting 객체를 모른다.

    }
}

4-3 추이성

null 이 아닌 모든 참조값 x,y gotj x.equals(y)가 true 이고 y.equals(z)가 true 라면 x.equals(z) 또한 true야 한다.

4-4 일관성

null 이 아닌 모든 참조 값 x,y 에 대해 x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다. 두 객체가 한번 같다면 영원히 같아야 한다. 즉 equals의 판단에 신뢰할 수 없는 자원이 들어가선 안된다는 말이다.


URL url1 = new URL("www.site-name.co.kr");
URL url2 = new URL("www.site-name.co.kr");

System.out.println(url1.equals(url2)); //?

Categories:

Updated: