views:

109

answers:

3

Question
This question was originally to find out if anyone knows of a way to modify the Eclipse equals() and hashCode() templates to generate the following pattern for Strings (making null equivalent to "").

if ([value] == null || [value].length() == 0) {
    if (other.[value] != null && other.[value].length() > 0) {
        return false;
    }
} else if (![value].equals(other.[value])) {
    return false;
}

One suggested solution
A lot of answers have suggested normalizing input in the setters (either converting all empty strings to nulls, or nulls to empty strings). I find that dangerous. Here are two legitimate pieces of code that will crash the program if the user isn't aware of this side effect.

If all empty strings are made nulls, this will crash:

myObject.setName("");
String uppercaseName = myObject.getName().toUpperCase();

If all nulls are made empty strings, this will crash (debated advisability of this aside, I would generally expect it to work):

myObject.setName(myStringSubclassInstance);
MyStringSubclass string = myObject.getName().someCustomSubclassMethod();

If I went down this path, I would make the setters throw an InvalidArgumentException and spell that out in the documentation. That's the right way IMHO.

Better Solution
Ultimately, I don't think any of the recommended solutions were appropriate. This is not so much their fault, as it is the fault of the original question. After considering the feedback, I agree that the original question was predicated upon an inadvisable solution. I can't delete it though, so I've edited it to avoid misleading others.

What I decided to go with was utilizing the Builder Pattern to normalize values in the build() method. This results in objects that always have either null or empty Strings, but without adding side effects to setters or the equals() method.

+3  A: 

This might work for your application, but in general it's not really a good idea, especially for a template to apply globally. Null and "" are not the same thing in lots of cases - for example, if you consider these two as identical how can you distinguish between uninitialized and existing but empty values?

You might also consider initializing default values or using the apache EqualsBuilder

Steve B.
Yes, I would not want to apply it everywhere. Only in select classes. I'll check that EqualsBuilder out tomorrow and see if it works for me.
DougW
+1  A: 

The trouble with your approach is null and empty Strings really are different values as far as the Java language is concerned, and even the String.equals() method treats null and an empty String as non-equal. So you need to replace all your String.equals comparisons for the fields with comparisons that deal with the null case.

This presents you with a bit of work, but your options include:

  • write your classes so that fields are never null,
  • write your own implementations of equals and hashCode for the classes, or
  • write your own wrapper class for the Strings.

The first is probably the best approach, especially if take it a step further and get rid of other code where null String values can creep in.

Stephen C
@Stephen C I actually think the first option is more dangerous. You'd have to override the setter functions to either throw an exception for nulls (annoying), or set an empty string for nulls (unexpected, potentially dangerous). We could get into builder patterns, etc, but what I really want is just a template engine for option #2. #3 would be very dangerous since these strings may get passed to other places that don't expect that functionality.
DougW
@DougW - if your application design says that `null` and `""` mean the same thing, then you *should* treat them the same and normalize them in the setter. It is not dangerous. It is simply implementing your design. As far as wanting a template engine to do the work for you, Eclipse doesn't implement this AFAIK. You might want to reflect on why not. My theory is that they (would have) concluded that it is the wrong approach.
Stephen C
@Stephen C - I appreciate your point. I don't think Eclipse not including something is much of an indication of its appropriateness though. I'm just not sold on the safety of converting nulls to empty strings in setters though. If someone sets a property, they expect it to be what they just set it to. It just happens to be equivalent from the object comparison perspective.
DougW
@DougW - IMO, it is more dangerous / inconsistent to say `""` and `null` mean the different things wrt getters and setters, but the same thing for equality. Maybe the real problem here is that the semantics you want for `equals` is not the "natural" semantics. Either way, the best solution (IMO) is to be consistent, one way or the other.
Stephen C
@Stephen C - I really don't understand this notion that the equals() method isn't subjective. Look at the String class. It disregards all sorts of properties when testing for equivalence. That's why they didn't implement a reflective equals() method in Object that just compares every property. Because it's up to the programmer to decide when two objects should be considered equal. That is completely legit... all I was looking for was a macro to make my particular pattern easier.
DougW
@DougW - as I said, I don't think such a macro / template / whatever exists. Move along please ... :-)
Stephen C
@DougW - it is a fairly well recognized fact that the concept of `equals` is problematic in OO programming.
Stephen C
A: 

Well if you do not care about null vs "", you could normalize the values inside of your class so they are always "". Then you would never be comparing nulls in your equals() method.

gpeche
@gpeche Agreed. But the problem is how? Writing setters that throw invalidargument exceptions, or intercept nulls and translate them into empty Strings (baaaad idea)? Throwing exceptions would be alright, but then I've got the same problem of generating the code automatically. And I've also made a design decision based on the availability of a template engine, which makes me cringe.
DougW
I do not understand why converting `null` to `""` (or the reverse) inside your class is a bad idea. After all, in that context they are equal to you.
gpeche
@gepeche I can't add readable code here, so I'll add an edit to my question to explain why that's dangerous.
DougW
I have seen your edit. You are assumming in that code that a setter merely assigns to a field, which is exactly the assumption we want to avoid when we implement setters. Anyway, the second example code is impossible as String is a final class.
gpeche