Java – Is it a bad habit to (over)use reflection

javaprogramming practicesreflection

Is it a good practice to use reflection if greatly reduces the quantity of boilerplate code?

Basically there is a trade-off between performance and maybe readability on one side and abstraction/automation/reduction of boilerplate code on the other side.

Edit: Here is an example of a recommended use of reflection.

To give an example, suppose there is a an abstract class Base which has 10 fields and has 3 subclasses SubclassA, SubclassB and SubclassC each with 10 different fields; they are all simple beans. The problem is that you get two Base type references and you want to see if their corresponding objects are of same (sub)type and are equal.

As solutions there is the raw solution in which you first check if the types are equal and then check all fields or you can use reflection and dynamically see if they are of the same type and iterate over all methods that start with "get" (convention over configuration), call them on both objects and call equals on the results.

boolean compare(Base base1, Base, base2) {
    if (base1 instanceof SubclassA && base2 instanceof SubclassA) { 
         SubclassA subclassA1 = (SubclassA) base1;
         SubclassA subclassA2 = (SubclassA) base2;
         compare(subclassA1, subclassA2);
    } else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
         //the same
    }
    //boilerplate
}

boolean compare(SubclassA subA1, SubclassA subA2) {
    if (!subA1.getField1().equals(subA2.getField1)) {
         return false;
    }
    if (!subA1.getField2().equals(subA2.getField2)) {
         return false;
    }
    //boilerplate
}

boolean compare(SubclassB subB1, SubclassB subB2) {
    //boilerplate
}

//boilerplate

//alternative with reflection 
boolean compare(Base base1, Base base2) {
        if (!base1.getClass().isAssignableFrom(base2.getClass())) {
            System.out.println("not same");
            System.exit(1);
        }
        Method[] methods = base1.getClass().getMethods();
        boolean isOk = true;
        for (Method method : methods) {
            final String methodName = method.getName();
            if (methodName.startsWith("get")) {
                Object object1 = method.invoke(base1);
                Object object2 = method.invoke(base2);
                if(object1 == null || object2 == null)  {
                    continue;
                }
                if (!object1.equals(object2)) {
                    System.out.println("not equals because " + object1 + " not equal with " + object2);
                    isOk = false;
                }
            }
        }

        if (isOk) {
            System.out.println("is OK");
        }
}

Best Answer

Reflection was created for a specific purpose, to discover the functionality of a class that was unknown at compile time, similar to what the dlopen and dlsym functions do in C. Any use outside of that should be heavily scrutinized.

Did it ever occur to you that the Java designers themselves encountered this problem? That's why practically every class has an equals method. Different classes have different definitions of equality. In some circumstances a derived object could be equal to a base object. In some circumstances, equality could be determined based on private fields without getters. You don't know.

That's why every object who wants custom equality should implement an equals method. Eventually, you'll want to put the objects into a set, or use them as a hash index, then you'll have to implement equals anyway. Other languages do it differently, but Java uses equals. You should stick to the conventions of your language.

Also, "boilerplate" code, if put into the correct class, is pretty hard to screw up. Reflection adds additional complexity, meaning additional chances for bugs. In your method, for example, two objects are considered equal if one returns null for a certain field and the other doesn't. What if one of your getters returns one of your objects, without an appropriate equals? Your if (!object1.equals(object2)) will fail. Also making it bug prone is the fact that reflection is rarely used, so programmers aren't as familiar with its gotchas.