이펙티브 자바 아이템 10을 읽던 중 의문점이 하나 생겼다.
equals를 재정의 하는 다양한 방법들이 있는데 그 중에는 같은 클래스인지 확인하기 위해 getClass 를 사용하는 경우와 instanceof를 사용하는 경우가 있었다.
책에서는 getClass를 통해 먼저 클래스를 확인하고 이후 값을 비교하게 되면 Liskov의 원칙을 위배하게 된다고 한다.
하지만 IDE 에서 자동 equals 생성을 할 시
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Person person = (Person) o;
return Objects.equals(name, person.name);
}
}
이처럼 getClass를 통해서 확인하도록 만들어져 있다. 그렇다면 이건 잘못된 것인가? 궁금해졌다.
Liskov원칙을 위배하게 된점은
"어떤 타입의 중요한 속성이라면 그 하위 타입에서도 마찬가지로 중요하다. 따라서 그 타입의 모든 메서드가 하위 타입에서도 똑같이 잘 작동해야한다." [Liskov87]
라는 점을 위배하게 된 것인데 예를 들어 위를 상속받은 Developer 라는 클래스가 있다고 하자.
class Developer extends Person {
String job;
public Developer(String name, String job) {
super(name);
this.job = job;
}
}
이 Developer 는 job라는 직군이 추가된 클래스이다.
Person p = new Person("민수");
Developer d = new Developer("민수", "BE");
System.out.println(p.equals(d)); // false
두 객체를 생성하고 비교하면 이름이 같으면 같은 객체라고 나타내도록한 equals가 정상적으로 작동하지 않는다.
Person을 상속받은 하위타입인 Developer에서 equals 메서드가 작동하지 않은것이다.
이러한 점에서 Liskov원칙을 위배할 수 있기에 getClass() 보다 instanceof를 사용한 비교를 하도록 권장하는 것 같다.
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Person)) {
return false;
}
return ((Person) o).name == name;
}
}
class Developer extends Person {
String job;
public Developer(String name, String job) {
super(name);
this.job = job;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Developer)) {
return super.equals(o);
}
Developer d = (Developer) o;
return name == d.name && job == d.job;
}
}
이렇게 말이다.
하지만, 여기서 하나 의문점이 또 생겨났다. 과연 instanceof로 만든 equals는 문제가 없을까 ?
실제로 좀 조사를 해보니 좀 많이 예전이긴 하지만 작가피셜 effetive java 에서 가장 논쟁점이 많은 부분이 이 item10 이라고 한다.
(https://www.artima.com/articles/josh-bloch-on-design#part17 에서 instanceof versus getClass in equals Methods 항목을 보면 joshua bloch가 인터뷰한 내용을 확인할 수 있다.)
Stack Overflow에서도 활발한 토론이 이루어졌다.
Bill Venners 는 getClass를 사용한 이유를 구현하고 있는 클래스를 상속받은 하위클래스에서 어떤일이 일어날지 모르기 때문에 상위타입의 equals를 사용하여 비교하는 것은 하위타입에 오히려 맞지 않는다는 입장인것같다.(정확하지 않을 수 있다.....)
Joshua Bloch는 Java 의 Collections(List, Map, Set...)의 동작방식이 equals를 이용하기에 getClass를 사용하면 하위타입이 들어맞지 못하는 경우가 발생하기에 instanceof를 통해 같도록 만들어서 Liskov원칙을 위배하지 않도록 하는게 더 낫다는 입장인 것 같다.
둘다 정확히 정답은 없는 것 같다.
객체 설계를 잘 한다면 중요한 필드를 통해 상위, 하위 관계없이 비교가 잘 되도록 필드 구성을 할 수 있기에 instanceof를 쓰면 좋을 것 같긴하지만 하위 클래스가 개념적으로 틀어지는 경우에는 상위 클래스의 equals를 사용하는 것이 정말 맞는지, 동등하게 결과가 나오는 게 맞는지를 따져서 객체의 비즈니스적인 성격에 따라 변경될 것 같다. 그럴때는 getClass를 통해 다른 객체로 인식되도록 하는 것이 좋지 않을까...(객체 설계가 잘못됬을 수도 있을 것 같다.)
'Programming > JAVA' 카테고리의 다른 글
Java HashSet에서 HashCode를 변경하게 되면... (0) | 2023.01.03 |
---|---|
Objects requireNonNull 란? (0) | 2022.12.30 |