views:

219

answers:

4

Hey, long time listener first time caller, and I'm asking a question related to Java reflection, and how easy it lends itself to apparently ugly coding. The following method attempts to take two similar objects (one object having all of the fields of the other object, and then some) and compare the two of them for equality. It will (allegedly) return true if the getters that the objects share are equal, and will return false if they are not equal.

public boolean validateArchive( Object record, Object arcRecord ) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
{
    log.debug( record.getClass().toString() );

    Object methodValue;
    Object arcMethodValue;

    for ( Method method : record.getClass().getMethods() )
    {
        if ( method.getTypeParameters().length == 0 && method.getName().startsWith( "get" ) && !method.getName().startsWith( "getClass" ) )
        {
            methodValue = method.invoke( record );
            arcMethodValue = arcRecord.getClass().getMethod( method.getName() ).invoke( arcRecord );

            log.debug( "Method name: " + method.getName() );
            log.debug( "Archive value: " + arcMethodValue );
            log.debug( "Object value: " + methodValue );

            if ( arcMethodValue != null && methodValue != null && !arcMethodValue.equals( methodValue ) )
            {
                return false;
            }
            else
            {
                if ( arcMethodValue == null && methodValue != null || methodValue == null && arcMethodValue != null )
                {
                    return false;
                }
            }
        }
    }

    return true;
}

This method does what I expect it to do in the unit tests, but it looks ugly, and feels wrong (I'm particularly not a fan of the nested 'if'). I was just hoping for some pointers on how to do this more effectively/efficiently. If I've broken some kind of posting rule, feel free to correct me, I am eager to learn, etc.

+4  A: 

For this particular task I would recommend implementing the equals method in the classes that will be compared, unless you don't have that option (for example, if you don't have the source code for the original class). IDE's like IntelliJ provide support for the "equals" and "hashcode" methods creation (in IntelliJ, you provide the fields that will be compared and which fields can be null or not). For this particular case, I would say go with these tools.

There is one case where I think that an implementation using Reflection would be useful - in a unit test, if you want to throw an assertion error if the equality is no true, you can actually throw an assertion for the exact field that is failing, and not just a generic "object is not the same" assertion error - even in this case, I would perform an equals after my original validation just to be sure that the objects are the same, or at least that the equals method is implemented and working properly.

PS: If you want to get rid of all this coding check the Beans Common library; PS 2: Reflection is not bad, and it is used everywhere where you don't have an explicit code call - Spring configuration files, for example. Just don't abuse it.

Ravi Wallau
I do not have control over either of the original classes in this situation (they are generated by iBator/iBatis, if you're curious why), which is why I have not simply made one inherit from the other, otherwise I would have, cleaning this up quite a bit. Neither of the expected objects will have any meaningful equals method available, unfortunately.
Charlie
I do feel like what I'm doing is a little abusive to Reflection, I just can't really think of a better way to get this task done. The objects being passed in could have any number of fields, and I don't know what they are, only that they will have getters for each one, and that the arcRecord object will always have the same getters as the primary object.
Charlie
+1  A: 

As you are looking only for "properties" you could use commons beanutils and more precisely this class...

(I guess you cannot implement .equals because your objects don't share the same type...)

pgras
That just wraps the ugliness behind another API. All that reflection crap is still there, it's just hidden behind a library.
KitsuneYMG
+1  A: 

I believe you're testing for unnecessary cases in your if statements, and can reduce them to this:

if ( arcMethodValue == null ) {
    if ( methodValue != null) {
        return false;
    }
} else if ( !arcMethodValue.equals( methodValue ) ) {
    return false;
}

Also, you're calling method.getName() 4 times, you could create a variable and then use it.

JRL
Excellent, thanks for the tips.
Charlie
See my answer for a better idiom.
polygenelubricants
@polygene: your answer is wrong.
JRL
I've edited out the `return` out to leave only the expression part of the idiom. I never said that he should put that in there as it is (which is why I renamed the variables). I'm only sharing the idiom.
polygenelubricants
A: 

Your return code is unnecessarily complicated. The standard idiom for comparison of reference types is this:

  (field1 == null) ? (field2 == null) : field1.equals(field2);

This is much clearer and is pretty standard (Effective Java 2nd Edition, page 43).


Update:

There was a confusion since earlier I had written return [idiom]. Yes, in this case a return would not be what the original poster want. He would want if ![idiom] return false; instead. My point is that the [idiom] works, and it's much better.

polygenelubricants
Thank you for this, I knew I was being way too complex with that.
Charlie
Upon further inspection, the problem with this is that, in the place where the nested if lies, it should never return true, where this will return true if field2 is null but field1 is not, which is bad.
Charlie
Then instead of `return [idiom];`, you do `if ![idiom] return false;`. I was just sharing the idiom, not telling you to put that in there as is (which is why I renamed things). I've edited the return out of the answer to make this even clearer.
polygenelubricants