java


Implementation of equals(): compare against implemented interface or implementing class?


I'ver been wondering how to best implement equals() for a family of classes that all implement the same interface (and the client is supposed to work only with said interface and never to know about implementing classes).
I haven't cooked up my own concrete example, but there are two examples in the JDK - java.lang.Number and java.lang.CharSequence that illustrate the decision:
boolean b1 = new Byte(0).equals( new Integer(0) ) );
or with CharSequence
boolean b2 = "".equals(new StringBuilder());
Would you ideally want those to evaluate to true or not? Both types do implement the same datatype interface, and as a client working with Numbers (resp. CharSequences) instances I would have an easier life if equals would compare the interface types instead of the implementing types.
Now this is not an ideal example, as the JDK exposes the implementing types to the public, but suppose we had not have to uphold compatibility with what is already there - from a designers point of view: Should equals check against the interface or is it better the way it is, checking against the implementation?
Note: I understand that checking for equality against an interface can be very hard to actually implement properly in practice and its made even more tricky since equal interfaces also need to return the same hashCode().
But those are only obstacles in implementation, take for example CharSequence, although the interface is pretty small, everything required for equality checks is present whithout revealing the internal structure of the implementation (so it is principally possible to implement properly, even without knowing about future implementations in advance).
But I am more interested in the design aspect, not on how to actually implement it. I wouldn't decide solely based on how hard something is to implement.
I would normally assume that "similar" objects would not be equal - for example I wouldn't expect the Integer(1) would pass equals(Long(1)) . I can imagine situations where that would be reasonable, but as the jdk needs to be a general-purpose API you wouldn't be able to make the assumption that that would always be the correct thing to do.
If you've got some sort of custom objects where it's reasonable, I think it's perfectly fine to implement an expanded definition of equals if you
are sure that you don't have some edge cases where you really do need the more specific equality (i.e. that would require the identical classes)
document it very clearly
make sure that hashcode behaves consistently with your new equals.
Define an abstract class that implements your interface and defines final equals()/hashCode() methods and have your customers extend that instead:
public interface Somethingable {
public void something();
}
public abstract class AbstractSomethingable implements Somethingable {
public final boolean equals(Object obj) {
// your consistent implementation
}
public final int hashCode() {
// your consistent implementation
}
}
Notice that by making your class abstract, you can implements the interface without defining the interface's methods.
Your customers still have to implement the something() method, but all their instances will use your code for equals()/hashCode() (because you've made those methods final).
The difference to your customers is:
Using the extends keyword instead of the implements keyword (minor)
Not being able to extend some other class of their choosing to use your API (could be minor, could be major - if it's acceptable then go for it)
For what it's worth, I'd probably do an implementation-specific equals implementation (side note - don't forget to implement hashCode...). Interface-level equals() puts a pretty heavy burden on implementers of the interface - who might or might not be aware of the special requirement.
Often, implementation-level works fine as your client only deals with one implementation (i.e. MyNumberProcessor can works on any Number, but practically one instance of it would only have to handle Long and maybe another only Double). Generics are a great way of making sure that happens.
In the rare case where it does matter, I would probably design the client to allow injection of a Comparator or - when not available - encapsulate my Numbers into a VarTypeNumber.
I'd try to add another equals Method to my interface. How about that:
assertFalse(new Integer(0).equals(new Byte(0))); // pass
assertTrue(new Integer(0).valueEquals(new Byte(0))); // hypothetical pass
This does not produce unexpected behaviour (different types equal) but keeps the possibility open to check for equal values.
There's a somewhat related topic in effective java where equals with instanceof and getClass is discussed. Can't remember the item number, though.
I would consider any implementation of equals that returns true for two objects that do not have the same concrete type to be extremely 'surprising' behavior. If you're operating inside a box where you know at compile time every possible implementor of the interface, you can fabricate equals that make sense with only interface methods, but that's not a reality for API/framework code.
You can't even be sure that nobody's going to write an implementation of the interface that mutates its internal state when you call the methods that you used to implement equals! Talk about confusing, an equals check that returns true and invalidates itself in the process?
--
This is what I understood to be the question as far as 'checking equality against the interface':
public interface Car {
int speedKMH();
String directionCardinal();
}
public class BoringCorrolla implements Car {
private int speed;
private String directionCardinal;
public int speedKMH() { return speed; }
public String directionCardinal() { return directionCardinal; }
#Override
public boolean equals(Object obj) {
if (obj isntanceof Car) {
Car other = (Car) obj;
return (other.speedKMH() == speedKMH() && other.directionCardinal().equals(directionCardinal());
}
}
}
public class CRAZYTAXI implements Car, RandomCar {
public int speedKMH() { return randomSpeed(); }
public String directionCardinal() { return randomDirection();}
}
It is possible to define equality among different classes.
In your case, the exact equality algorithm must be specified by the interface, so any class implementing the interface must abide by it. Better yet, since the algorithm depends only on information exposed by the inferface, just implement it already, so subclasses can simply borrow it.
interface Foo
class Util
static int hashCode(Foo foo){ ... }
static boolean equal(Foo a, Foo b){ ... }
static boolean equal(Foo a, Object b)
return (b instanceof Foo) && equal(a, (Foo)b);
class FooX implements Foo
int hashCode()
return Util.hashCode(this);
boolean equals(Object that)
return Util.equal(this, that);

Related Links

Android Studio Test Build Failed - app:transformClassesAndResourcesWithProguardForDevelopmentDebugAndroidTest
How create Native memory allocation (mmap) error in java
Grid layout columns alignment
Postgresql JSON data truncated with JDBC
How can I get the size of the rows and columns from input text file?
Animated GIF, displayed on JPanel, not screenshoting in file with paint method, but JPG and PNG are ok
JOptionPane goes away after reader
Issue while Connecting to FTPS server using apache commons java
Which Date format does Android's Telephony API use? [duplicate]
Unecessary logs in headless testing using phantom js and selenium
How to iterate over a JSONArray in java 8
“non-static variable this cannot be referenced from a static context” error [duplicate]
Project using array fields in MongoDB Java Driver
Why doesn't this throw a NullPointerException?
Posting dynamic JSON-Object to Spring RESTful Web Service
Invalid flag error when compiling a correct program in server

Categories

HOME
ionic-framework
cloud
c#-4.0
google-play
onedrive
grep
yahoo-oauth
infragistics
graphql
jpeg
angular-ui-bootstrap
cross-browser
acquia
wamp
constraint-programming
vifm
orchardcms
messages
flask-wtforms
iolanguage
finite-automata
invantive-sql
http-status-code-504
sms-gateway
shared-hosting
dbext
paging
java-3d
one-to-many
poltergeist
iframe-resizer
yadcf
http-status-code-503
data-manipulation
grails-3.1
android-nestedscrollview
normal-distribution
force-layout
html5-fullscreen
wpf-controls
jmonkeyengine
x11-forwarding
serverside-rendering
xenforo
webix-treetable
ssjs
sql-server-agent
quadratic-programming
texmaker
nxlog
fault
apache-fop
upstart
pubmed
datastax-startup
sage-one
tropo
python-cryptography
mako
hittest
dynamics-sl
dwscript
migradoc
objective-c-swift-bridge
android-fonts
google-feed-api
flash-cs5
make-install
packagist
captivenetwork
qtableview
lib.web.mvc
iis-arr
operation
key-management
jscript.net
angularjs-ng-click
client-side-templating
html-helper
phpthumb
datagridviewcolumn
centos5
openlaszlo
imdbpy
bulkloader
ril
back-stack
padarn
reddot
netbeans-6.9
genshi
invite
propagation
jmock
yslow
jquery-ui-droppable
ncqrs

Resources

Mobile Apps Dev
Database Users
javascript
java
csharp
php
android
MS Developer
developer works
python
ios
c
html
jquery
RDBMS discuss
Cloud Virtualization
Database Dev&Adm
javascript
java
csharp
php
python
android
jquery
ruby
ios
html
Mobile App
Mobile App
Mobile App