26

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");
        }
}
4
  • 24
    Overuse of anything is a bad habit. Commented Apr 1, 2013 at 12:49
  • 1
    @user61852 Right, too much freedom leads to dictatorship. Some old Greek already knew about this.
    – ott--
    Commented Apr 1, 2013 at 15:57
  • 6
    “Too much water would be bad for you. Obviously, too much is precisely that quantity which is excessive—that’s what it means!” — Stephen Fry
    – Jon Purdy
    Commented Apr 1, 2013 at 19:25
  • 4
    "Anytime you find yourself writing code of the form "if the object is of type T1, then do something, but if it's of type T2, then do something else," slap yourself. javapractices.com/topic/TopicAction.do?Id=31
    – rm5248
    Commented Apr 1, 2013 at 21:48

6 Answers 6

29

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.

1
  • "Reflection was created for a specific purpose, to discover the functionality of a class that was unknown at compile time" -- can you prove this with a reference, please?
    – yegor256
    Commented Dec 12, 2021 at 13:00
13

Overusing of reflection probably depends on the language used. Here you are using Java. In that case, reflection should be used with care because often it is only a workaround for bad design.

So, you are comparing different classes, this is a perfect problem for method overriding. Note that instances of two different classes should never be considered equal. You can compare for equality only if you have instances of the same class. See https://stackoverflow.com/questions/27581/overriding-equals-and-hashcode-in-java for an example how to implement equality comparison correctly.

9
  • 21
    +1 - Some of us believe ANY use of reflection is a red flag indicating bad design. Commented Apr 1, 2013 at 10:53
  • 1
    Most probably in this case a solution based on equals is desirable, but as a general idea what is wrong with the reflection solution? Actually it is very generic and the equals methods do not need to be explicitly written in every class and subclass (although they can easily be generated by a good IDE).
    – Random42
    Commented Apr 1, 2013 at 11:00
  • 2
    @RossPatterson Why?
    – Random42
    Commented Apr 1, 2013 at 11:00
  • 2
    @m3th0dman Because it leads to things like your compare() method assuming that any method beginning with "get" is a getter and is safe and appropriate to call as part of a comparison operation. It's a violation of the object's intended interface definition, and while that may be expedient, it is almost always wrong. Commented Apr 1, 2013 at 11:04
  • 4
    @m3th0dman It violates encapsulation - the base class has to access attributes in its subclasses. What if they are private (or getter private)? What about polymorphism? If I decide to add another subclass and I want to do the comparison differently in it? Well, the base class is already doing it for me and I can't change it. What if the getters lazy-load something? Do I want comparison method to do that? How do I even know a method starting with get is a getter and not a custom method returning something?
    – Sulthan
    Commented Apr 1, 2013 at 11:15
3

I prefer to avoid reflective programming as much as possible because it

  • makes the code harder to statically check by the compiler
  • makes the code harder to reason about
  • makes the code harder to refactor

It's also much less performant than simple method calls; it used to be slower by an order of magnitude or more.

Statically Checking Code

Any reflective code looks up classes and methods using strings; in the original example it's looking for any method starting with "get"; it will return getters but other methods in addition, such as "gettysburgAddress()". The rules could be tightened up in code, but the point remains that it's a runtime check; an IDE and the compiler can't help. In general I dislike "stringly typed" or "primitive obsessed" code.

Harder to Reason About

Reflective code is more verbose than simple method calls. More code = more bugs, or at least more potential for bugs, more code to read, to test, etc. Less is more.

Harder to Refactor

Because the code is stringly/dynamically based; as soon as reflection is on the scene you can't refactor code with 100% confidence using an IDE's refactoring tools as the IDE cannot pick up the reflective uses.

Basically, avoid reflection in general code if at all possible; look for an improved design.

1

Overuse of anything by definition is bad, right? So let's get rid of the (over) for now.

Would you call spring's amazingly heavy internal use of reflection a bad habit?

Spring tames reflection by using annotations--so does Hibernate (and probably dozens/hundreds of other tools).

Follow those patterns if you use it in your own code. Use annotations to ensure your user's IDE can still help them (Even if you are the only "User" of your code, careless use of reflection will probably come back to bite you in the butt eventually).

However without consideration into how your code will be used by developers even the simplest use of reflection is probably over-use.

1

I think you have two issues here.

  1. How much dynamic vs static code should I have?
  2. How do I express a custom version of equality?

Dynamic vs Static Code

This is a perennial question and the answer is very opinionated.

On the one hand your compiler is very good at catching all sorts of bad code. It does this through various forms of analysis, Type analysis being a common one. It knows that you cannot use a Banana object in code that is expecting a Cog. It tells you this through a compilation error.

Now it can only do this when it can infer both the accepted Type, and the given Type from context. How much can be inferred, and how general that inference is, largely depends on the language being used. Java can infer type information through mechanisms such as Inheritance, Interfaces, and Generics. Mileage does vary, some other languages provide fewer mechanisms, and some provide more. It still boils down to what the compiler can know to be true.

On the other hand your compiler cannot predict the shape of foreign code, and sometimes a general algorithm can be expressed over many types that can not easily be expressed using the language's type system. In these cases the compiler cannot always know the outcome in advance, and it may not even be able to know what question to ask. Reflection, interfaces, and the Object class are Java's way of handling these problems. You will have to provide the correct checks and handling, but its not unhealthy to have this sort of code.

Whether to make your code very specific, or very general comes down to the problem you are trying to handle. If you could easily express it using the type system do so. Let the compiler play to its strengths and help you. If the type system could not possibly know in advance (foreign code), or the type system is a poor fit for a general implementation of your algorithm then reflection (and other dynamic means) are the right tool to use.

Just be aware that stepping outside the type system of your language is surprising. Imagine walking up to your friend and starting a conversation in English. Suddenly drop a few words from Spanish, French, and Cantonese in that express your thoughts precisely. Context will tell your friend a lot, but they also may not know quite how to handle those words leading to all sorts of misunderstandings. Is dealing with those misunderstandings better than explaining those ideas in English using more words?

Custom Equality

While I understand that Java relies heavily on the equals method for generally comparing two objects its not always suitable in a given context.

There is another way, and it is a Java standard too. Its called a Comparator.

As to how you implement your comparator will depend on what you are comparing, and how.

  • It can be applied to any two objects regardless of their specific equals implementation.
  • It could implement a general (reflection based) comparison method for handling any two objects.
  • The boilerplate comparison functions could be added for commonly compared object types, granting type safety and optimisation.
0

I think most of these answers miss the point.

  1. Yes, you should probably write equals() and hashcode(), as noted by @KarlBielefeldt.

  2. But, for a class with many fields, this can be tedious boilerplate.

  3. So, it depends.

    • If you only need equals and hashcode rarely, it's pragmatic, and probably o.k., to use a general purpose reflection calculation. At least as a quick and dirty first pass.
    • but if you need equals a lot, e.g. these objects get put into HashTables, so that performance will be an issue, you should definitely write out the code. it will be way faster.
  4. Another possibility: if you classes really have so many fields that writing an equals is tedious, consider

    • putting the fields into a Map.
    • you can still write custom getters and setters
    • use Map.equals() for your comparison. (or Map.hashcode())

e.g. ( note: ignoring null checks, should probably use Enums instead of String keys, much not shown... )

class TooManyFields {
  private HashMap<String, Object> map = new HashMap<String, Object>();

  public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
  public int getFoo()  { return map.get("Foo").intValue(); }

  public setBar(Sttring s) { map.put("Bar", s); }
  public String getBar()  { return map.get("Bar").toString(); }

  ... more getters and setters ...

  public boolean equals(Object o) {
    return (o instanceof TooManyFields) &&
           this.map.equals( ((TooManyFields)o).map);
}
1
  • Regarding your second point, Java 7 introduced Objects.hash, which obviates some of the tedium involved in overriding Object#hashCode.
    – jub0bs
    Commented Aug 2, 2020 at 16:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.