-
-
Save rocketraman/653af25ee3bf72ca497f to your computer and use it in GitHub Desktop.
import com.google.common.base.MoreObjects; | |
import java.util.Objects; | |
public class Address { | |
... | |
@Override | |
public boolean equals(Object obj) { | |
if (obj == null) return false; | |
if (getClass() != obj.getClass()) return false; | |
final Address other = (Address) obj; | |
return Objects.equals(this.houseNumber, other.houseNumber) | |
&& Objects.equals(this.street, other.street) | |
&& Objects.equals(this.city, other.city) | |
&& Objects.equals(this.stateOrProvince, other.stateOrProvince) | |
&& Objects.equals(this.country, other.country); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash( | |
this.houseNumber, this.street, this.city, this.stateOrProvince, this.country); | |
} | |
@Override | |
public String toString() { | |
return MoreObjects.toStringHelper(this) | |
.add("houseNumber", houseNumber) | |
.add("street", street) | |
.toString(); | |
} | |
} |
public class EvilAddress extends Address {
@Override
public boolean equals(Object other) {
return true;
}
}
Address a = new Address(...);
Address b = new EvilAddress(...);
a.equals(b); // false
b.equals(a); // true
Granted it's an evil example, but it illustrates the point. If you make Address
final it solves the issue completely.
On the other hand, I think that this.getClass() == that.getClass()
is also too rigid - it prevents me substituting a legitimate alternative implementation of Address
, which happens to want to consider itself equal to the normal one.
That's a trivially silly example. Making Address
final, nor using instanceof
, does not "solve" this:
public class Foo {
public boolean equals(Object other) {
return true;
}
}
Address a = new Address(...);
Foo f = new Foo();
a.equals(f); // false
f.equals(a); // true
On the other hand, I think that
this.getClass() == that.getClass()
is also too rigid - it prevents me substituting a legitimate alternative implementation ofAddress
, which happens to want to consider itself equal to the normal one.
Yes, that's the Liskov Substitution Principle, and yes this implementation does not allow that. This gist isn't meant as a be-all and end-all that works in every situation, but a general implementation that works in many cases. Coming back full circle, as I said earlier, there are times when instanceof
is the right approach, if subclass substitution is important. However, as Angelika Lankers says:
Whether or not this is a problem fully depends on the semantics of the class and the purpose of the extension.
BTW, I also agree that "final by default" is a good idea -- if a class is meant to be extended it should be explicitly written as such. Classes are final by default in Kotlin, unless explicitly marked as "open" by the author.
@trejkaz I don't see how. If either
a
orb
is a different class, whether subclass or not, would meana.getClass() != b.getClass()
, thus maintaining symmetry. If you can violate symmetry with this code, please post an example.