Now why would I really care about the equals, hashcode, and toString methods you ask? Well, I started to follow what I read in Effective Java. At first I just overrode them by getting them to throw an UnsupportedOperationException, but then there were instances where we needed them. *sigh* It sure sucks to have to build those methods for classes that have 30+ fields. It's dumb, boring, easily messed up work. Something that the computer should do for me, not the other way around.
And than what happens when we add another field? Well, it's easy enough to add more getters and setters (since you can't use it till you do), but what about the equals? For each member you add, you'd have to modify 3 methods plus somehow remember to keep your tests all in sync too. PITA
This has a bigger impact than you might think at first. When you have your entity classes, how do you make sure that as you change your app all the new fields are actually getting persisted down and then back up? Most likely in your tests you are calling .equals() on them but this only works if your equals contains the fields you just added! And that you have put (with your test) a non-default value into the field.
Realistically, you know anyone would screw this up at some point and it could be a long time after it was in production before anyone actually starts to try and make reports or look for those fields.
At work on Friday I made a first step: I made a test method that if you passed in an object, it would fill in all the fields with a setter method with a "random" string. So at least the testing problem would be addressed.
Today I wrote a class (and some tests of course) that contains reflexive toString, equals and hashcode. Please feel free to use it and report back any problems that you see. If you can think of anything that is seriously wrong with it, let me know.
I keep on thinking that this is almost too good a solution. Why have other people not used this before as a somewhat lazy way to maintain some of your objects? My only guess right now is that it is a performance hit when you do things like this. I'm going to check it out later (tomorrow) and post what the results where. If it's not super slow, I think that I'll use these files to make my life easier.
Update: After doing some tests, my dynamic impl of equals is about 65 timers slower, hashcode about 50 times slower, and toString about 3-5 times slower. I've updated the classes online.
Listening to: Lemon Heads - Mrs. Robinson
couldn't the default object equals method, just check all the member variables, to see if they were equal. I don't know how this could be done in Java code, as I don't think there's a way to get dynamic names of member variables, but surely Sun could implement something like this. If you wanted to not include something, or perform some extra check, you could just overwrite it. It would be a good safecheck against people who just use equals without even realizing they have to implement it.
ReplyDeleteThere may be some cases where you don't want to confirm that *every* field is the same. This is probably why equals and hashCode weren't automated.
ReplyDeleteFor example, if an object had a field to hold its current state in a state machine. You may want to ignore the "state" the object is in and confirm that only the "value" of the objects is the same.
Exactly. It's done the way it is to give the user the most control with the least default overhead. I do think that Sun made the right decision, even if it can be a pain in the ass sometimes. I think that it would be far easier to get this type of code put into the struts impl. of the ActionForms rather than getting Sun to change the root object of all java classes.
ReplyDeleteIf you wanted to get things dynamically from a class, just check out the java.lang.reflect package (or the code that I posted!). The reason why I had to use getters to get the values instead of just accessing the members directly was because they are not visible (private).
I just wrote this code to solve a specific problem that I had. I hope that it will work well. I'll see on Monday. ;-)
BTW, be careful about doing performance metrics with the Java VM ... there are all sorts of problems.
ReplyDeleteRemember, Java Microbenchmarks are Evil:
http://www.ryanlowe.ca/blog/archives/001218.php
It's interesting that they would be different factors of perf difference though. I'm going to check out your code.
It's great you wrote unit tests. :) Did you also do jcoverage (code coverage) analysis of those tests? ;)
hee hee... thanks for the link. Ya, I remember that the complier does some optimization, so I tried to use the var's that I saved, but I'm not so sure it all worked. I wanted just a sanity check rather than real "hard" numbers.
ReplyDeleteNo, I didn't do jcoverage on those tests at all. Is there anyway to easily do jcoverage NOT through ant?
I'm pretty sure Ant is required.
ReplyDeleteCan someone explain to me what reflectiveEquals does?
ReplyDeleteIn what sense does it check if two objects are equal?
What it is supposed to do it call all the getter methods on a classes passed into it, and call .equals on those. If they are all equal, then the object is "equal".
ReplyDeleteReally, it's not the best solution. It would be better if the method was inside the class itself and and called that on all the members. However, I put it outside so that I'd be able to use it with many classes.
Does that make sense?
that's what I was saying, you could make the default one just check everything, and then it could be overwritten by the programmer. It would be nice to have something in there as default instead of nothing, or have it throw an exception if it's not actually implemented.
ReplyDeleteWell actually there is a default: Object's equals() checks if the two instances are equal.
ReplyDeleteThe downside for subclasses is that this isn't enough to check equality. The first thing I always do when I make a new class is override the equals(), hashCode() and toString() methods and make them throw UnsupportedOperationException until I implement them. Then I don't get weird results because some other class is using my class' hashCode() method or something.
If you don't want to write your own util, the wonderful people at apache have provided one.
ReplyDeletehttp://jakarta.apache.org/commons/lang/