자바 (17). Effective Java. 아이템10. Eqaul는 일반 규약을 지켜 재정의 하라
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 재정의 할 경우 지켜야 하는 규약
- 반사성
- 대칭성
- 추이성
- 일관성
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)); //?