There are many situations when reflection can “help” you. Let’s go through all of them and see why the coupling they add to the code is unnecessary and harmful.
Type Checking and Casting
Here is the code:
I’m not sure everybody would agree that this is reflection, but I believe it is: we check the structure of the class at runtime and then make a call to the method
size() which doesn’t exist in the
Iterable. This method only “shows up” at runtime, when we make a dynamic shortcut to it in the bytecode.
Why is this bad, aside from the fact that 1) it’s slow, 2) it’s more verbose and so less readable, and 3) it introduces a new point of failure since the object
items may not be an instance of class
Collection, leading to
The biggest problem the code above causes to the entire program is the coupling it introduces between itself and its clients, for example:
This method may work or it may not. It will depend on the actual class of
list. If it is
Collection, the call to
sizeOf will succeed. Otherwise, there will be a runtime failure. By looking at the method
calc we can’t tell what is the right way to handle
list in order to avoid runtime failure. We need to read the body of
sizeOf and only then can we change
calc to something like this:
This code seems to be OK so far. However, what will happen when
sizeOf changes its implementation to something like this (I took it from this article about casting):
sizeOf perfectly handles any type that’s coming in, whether it’s an instance of
Collection or not. However, the method
calc doesn’t know about the changes made in the method
sizeOf. Instead, it still believes that
sizeOf will break if it gets anything aside from
Collection. To keep them in sync we have to remember that
calc knows too much about
sizeOf and will have to modify it when
sizeOf changes. Thus, it’s valid to say that
calc is coupled with
sizeOf and this coupling is hidden: most probably, we will forget to modify
sizeOf gets a better implementation. Moreover, there could be many other places in the program similar to
calc, which we must remember to modify when the method
sizeOf changes. Obviously, we will forget about most of them.
This coupling, which is a big maintainability issue, was introduced thanks to the very existence of reflection in Java. If we had not been able to use
instanceof operator and class casting (or did not even have them), the coupling would not be possible in the first place.
Consider this code:
How would you write a unit test for this class and for its method
print()? Obviously, it’s almost impossible without refactoring the class. The method
System.out injectable as a dependency, but some of us believe that reflection is a better option, which would allow us to test the private method
name directly, without calling
You can also use PowerMock Java library to do many “beautiful” things with private methods.
The problem with this test is that it is tightly coupled with the object it tests: the test knows too much about the class
Book. The test knows that the class contains a private method
name. The test also knows that the method
name will at some point be called by the method
The main purpose of a unit test is to be a “safety net” for us programmers trying to modify the code that was written earlier or much much earlier: if we break anything, the tests give us a timely signal, “highlighting” the place where the code was broken. If nothing is highlighted and the tests are green I can continue modifying the code. I rely on the information from my tests. I trust them.
I take the class
Book and want to modify it, simply making the method
StringBuilder instead of
String. It’s a pretty innocent modification, which may be necessary for performance considerations. Before I start making any changes, I run all tests (it’s a good practice) and they all pass. Then I make my changes, expecting no tests to fail:
However, the test
BookTest will fail, because it expects my class
Book to have method
name which returns
String. If it’s not my test or I wrote it a long time ago, I would be frustrated to learn this fact: the test expects me to write my private methods only one specific way. Why? What’s wrong with returning
StringBuilder? I would think that there is some hidden reason for this. Otherwise, why would a test demand anything from a private implementation of a class? Very soon, after some investigation I would find out that there is no reason. It’s just an assumption the test made about the internals of
Book and this assumption has no reasons aside from “We didn’t have time to refactor the class and make
By the way, this testing approach is known as the “Inspector” test anti-pattern.
What would I do next? I would have to roll back my changes and then start refactoring the test and the class, in order to get rid of this assumption. However, changing the test and at the same time changing main code is, I believe, a dangerous practice: most probably I will introduce some new bugs.
The tests are not a “safety net” for me anymore. I can’t trust them. I modify the code and I know that I didn’t break anything. However, the test gives me a red signal. How can I trust it if it lies in such a simple scenario?
This coupling between the unit test
BookTest and the class
Book would not happen if it was not possible to use reflection in the first place. If nobody had the ability to reach the private method in any way, the Inspector anti-pattern in unit tests would not be possible.
Of course, life would be even better if we also didn’t have private methods.
Here is how a typical factory may work:
The factory method is
make. It expects the name of the “operator” to be provided and then, using
Class.forName() from the Java Reflection API, constructs the name of the class, finds it in the classpath, and makes an instance of it. Now, say there are two classes both implementing the interface
Then we use them, first asking our factory method to make objects from operator names:
result will be 13.
We would not be able to do this without reflection. We would have to do this instead:
If you ask me, this code looks much more readable and maintainable. First of all, because in any IDE that enables code navigation it would be possible to click on
OpPlus and immediately jump to the body of the class. Second, the logic of class finding is provided out-of-the-box by JVM: I don’t need to guess what happens when
make("Plus") is called.
There are a few reasons why people love static factories. I don’t agree with them. This blog post explains why. Without reflection it wouldn’t be possible to have static factories at all and the code would be better and more maintainable.
In Java you can attach an annotation (an instance of a DTO-ish interface) to a class (or an element of it like a method or an argument). The information from the annotation can then be read at runtime or compile time. In modern frameworks like Spring this feature is frequently used in order to automate objects wiring: you just attach some annotations to your classes and the framework will find them, instantiate them, place them into a DI container, and assign to other objects’ attributes.
I’ve said it earlier that this very mechanism of discovering objects and automatically wiring them together is an anti-pattern. I’ve also said earlier that annotations are an anti-pattern. Neither dependency injection containers, not auto-wiring, nor annotations would exist if there was no reflection. Life would be much better and Java/OOP much cleaner.
The clients of annotated objects/classes are coupled with them, and this coupling is hidden. An annotated object can change its interface or modify annotations and the code will compile just fine. The problem will surface only later at runtime, when the expectations of other objects won’t be satisfied.
When programmers don’t understand object-oriented paradigm, they make DTOs instead of proper objects. Then, in order to transfer a DTO over a network or save it to a file, they serialize or marshall them. It’s usually done by a special serialization engine, which takes a DTO, breaks all possible encapsulation barriers, reads the values of all of its fields, and packages them into, say, a piece of JSON.
In order to let the serialization engine break encapsulation barriers, a programming language has to have reflection. First, because some fields of a DTO may be private and thus accessible only through reflection. Second, even if a DTO is designed “right” with all necessary getters for the private fields, still reflection is required in order to understand which getters are present and can be called.
The attitude serialization expresses towards objects is very similar to what ORM does. Neither of them talk to objects, but instead they pretty “offensively” tear them apart, taking away what’s necessary, and leaving the poor objects unconscious. If in the future an object decides to change its structure, rename some fields, or change the types of returned values—other objects, which actually are coupled with the object through serialization, won’t notice anything. They will notice, but only at runtime, when “invalid data format” exceptions start floating up. The developers of the object won’t have a chance to notice that their changes to the interface of the object affect some other places in the code base.
We can say that serialization is a “perfect” method of coupling two objects such that neither one will know about it.
The very idea of object-oriented programming is centered around the principle that an object is king. An object and only an object may decide what to do with the data it encapsulates. The existence of this principle and adherence to it helps avoid runtime errors usually caused by a simple scenario: A uses the data coming from B without telling B how it’s being used, then B changes the format or semantics of the data, and A fails to understand it.
Obviously, serialization in such an “abusive” way would not be possible, if there was no reflection in the first place. A more careful serialization would be possible and would be used, not through reflection but via printers implemented by objects.
To conclude, reflection introduces coupling, which is hidden. This is the most dangerous type of coupling, because it’s hard to follow, it’s hard to find, and it’s hard to remove. Without reflection object-oriented design would be much cleaner and solid. But even if this feature does exist, I suggest you never use reflection in your programming language.