|
| 1 | +--- |
| 2 | +id: api.reflection |
| 3 | +title: "Introduction to java.lang.reflect.*" |
| 4 | +slug: learn/introduction_to_java_reflection |
| 5 | +type: tutorial |
| 6 | +category: api |
| 7 | +category_order: 1 |
| 8 | +layout: learn/tutorial.html |
| 9 | +subheader_select: tutorials |
| 10 | +main_css_id: learn |
| 11 | +description: "TODO" |
| 12 | +author: ["DrHeinzM.Kabutz"] |
| 13 | +--- |
| 14 | + |
| 15 | +<a id="reflection"> </a> |
| 16 | + |
| 17 | +## Reflection |
| 18 | + |
| 19 | +The face looking back from the mirror this morning told a story. |
| 20 | +I urgently needed a shave (and still do). And yesterday, whilst sitting |
| 21 | +outside in our garden, I should have worn sunscreen, or at least |
| 22 | +a hat. I had tried my hand at making my own bacon, not being able |
| 23 | +to find such delicacies on Crete, and the five hours of smoking |
| 24 | +in the Weber were put to good use writing a Java Specialists |
| 25 | +Newsletter about parallel streams and virtual threads (Issue 311). |
| 26 | +The reflection in the mirror blinked. Enough time staring, it was |
| 27 | +time to start writing about what I had just witnessed - reflection. |
| 28 | + |
| 29 | +Java reflection allows an object to look in the mirror and discover |
| 30 | +what fields, methods, and constructors it has. We can read and |
| 31 | +write fields, invoke methods, and even create new objects by |
| 32 | +calling the constructors. Just like the stubble on my face and |
| 33 | +the slight sunburn, we can see ourselves through others' eyes. |
| 34 | + |
| 35 | +Why should you care to read this tutorial? If you already know |
| 36 | +reflection, you might want to peek through for entertainment |
| 37 | +value. But if you have never heard of reflection, well, it's time |
| 38 | +to take a good look in the mirror and discover a new magic that will |
| 39 | +allow you to sometimes save thousands of fingerbreaking lines of code |
| 40 | +with a few well-positioned chunks of reflection poetry. Oh and did I |
| 41 | +mention that some employers post nasty interview questions that |
| 42 | +have an easy solution via reflection? I hope you will enjoy it and |
| 43 | +that you will try out the code snippets in the tutorial. Thank you |
| 44 | +for joining me on this journey. |
| 45 | + |
| 46 | +## The Class `Class` |
| 47 | + |
| 48 | +No, this is not a typo. There is a class called `Class`. And it is a |
| 49 | +subclass of `Object`. And `Object` has a `Class`. A nice circular dependency. |
| 50 | + |
| 51 | +How can we get hold of our object's Class? Each object has a |
| 52 | +`getClass()` method that it inherits from `java.lang.Object`. |
| 53 | +When we call that, we get back the actual implementation `Class`. |
| 54 | + |
| 55 | +For example, consider the following code. Note that for our code |
| 56 | +snippets we are using the new unnamed classes, which are a preview |
| 57 | +feature of Java 21. See [JEP 445](https://openjdk.org/jeps/445). |
| 58 | +We can run them directly with `java --enable-preview --source 21 GetClassDemo.java` |
| 59 | + |
| 60 | +```java |
| 61 | +// GetClassDemo.java |
| 62 | +import java.util.List; |
| 63 | +import java.util.ArrayList; |
| 64 | + |
| 65 | +// Using new Unnamed Classes which is a preview feature of Java 21. |
| 66 | +// See JEP 445 |
| 67 | +void main() { |
| 68 | + List<String> list1 = new ArrayList<>(); |
| 69 | + System.out.println(list1.getClass()); |
| 70 | + var list2 = new ArrayList<String>(); |
| 71 | + System.out.println(list2.getClass()); |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +In other words, it does not matter how the variable is declared, we |
| 76 | +always get the actual implementation object's class. How can we get |
| 77 | +the List class? That is fairly easy, using class literatls. We simply |
| 78 | +write the name of the class, followed by `.class`, like so: |
| 79 | + |
| 80 | +```java |
| 81 | +// ClassLiteral.java |
| 82 | +void main() { |
| 83 | + System.out.println(Number.class); // class java.lang.Number |
| 84 | + System.out.println(java.util.List.class); // interface java.util.List |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +We can also load classes by name as `String`, without even knowing |
| 89 | +whether the class will be available at runtime. For example, here |
| 90 | +we are loading whatever class we are entering on the `Console`: |
| 91 | + |
| 92 | +```java |
| 93 | +// ClassForName.java |
| 94 | +void main() throws ClassNotFoundException { |
| 95 | + var console = System.console(); |
| 96 | + String className = console.readLine("Enter class name: "); |
| 97 | + System.out.println(Class.forName(className)); |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +For example: |
| 102 | + |
| 103 | +```output |
| 104 | +heinz$ java --enable-preview --source 21 ClassForName.java |
| 105 | +Note: ClassForName.java uses preview features of Java SE 21. |
| 106 | +Note: Recompile with -Xlint:preview for details. |
| 107 | +Enter class name: java.util.Iterator |
| 108 | +interface java.util.Iterator |
| 109 | +``` |
| 110 | + |
| 111 | +Each class is loaded into a `ClassLoader`. The JDK classes all |
| 112 | +reside in the bootstrap class loader, whereas our classes are in |
| 113 | +the system class loader, also called application class loader. |
| 114 | +We can see the class loaders here: |
| 115 | + |
| 116 | +```java |
| 117 | +// ClassLoaderDemo.java |
| 118 | +void main() { |
| 119 | + System.out.println(String.class.getClassLoader()); |
| 120 | + System.out.println(this.getClass().getClassLoader()); |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +Interesting is that depending on how we invoke this code, we get |
| 125 | +different results. For example, if we call it using the |
| 126 | +`java ClassLoaderDemo.java`, then the type of class loader is a |
| 127 | +`MemoryClassLoader`, whereas if we first compile it and then call |
| 128 | +it with `java ClassLoaderDemo`, it is an `AppClassLoader`. The |
| 129 | +class loader for JDK classes comes back as `null`. |
| 130 | + |
| 131 | +```output |
| 132 | +heinz$ java --enable-preview --source 21 ClassLoaderDemo.java |
| 133 | +null |
| 134 | +com.sun.tools.javac.launcher.Main$MemoryClassLoader@6483f5ae |
| 135 | +``` |
| 136 | + |
| 137 | +And |
| 138 | + |
| 139 | +```output |
| 140 | +heinz$ javac --enable-preview --source 21 ClassLoaderDemo.java |
| 141 | +heinz$ java --enable-preview ClassLoaderDemo |
| 142 | +null |
| 143 | +jdk.internal.loader.ClassLoaders$AppClassLoader@3d71d552 |
| 144 | +``` |
| 145 | + |
| 146 | +The purpose of class loaders is to partition classes for security |
| 147 | +reasons. Classes in the JDK cannot see our classes at all, and |
| 148 | +similarly, classes in the `AppClassLoader` have no relation to |
| 149 | +classes in the `MemoryClassLoader`. This can cause some surprises |
| 150 | +when we compile our classes and then also launch them with |
| 151 | +the single-file command `java SomeClass.java` |
| 152 | + |
| 153 | +## Shallow Reflective Access |
| 154 | + |
| 155 | +Once we have the class, we can find out a lot of information about |
| 156 | +it, such as who the superclasses are, what public members it has, |
| 157 | +what interfaces it has implemented. If it is a `sealed` type, we |
| 158 | +can even find the subtypes. |
| 159 | + |
| 160 | +Let's try find the methods defined on java.util.Iterator: |
| 161 | + |
| 162 | +```java |
| 163 | +// MethodsOnIterator.java |
| 164 | +import java.util.Iterator; |
| 165 | +import java.util.stream.Stream; |
| 166 | + |
| 167 | +void main() { |
| 168 | + Stream.of(Iterator.class.getMethods()) |
| 169 | + .forEach(System.out::println); |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +We see four methods, two of which are `default` interface methods: |
| 174 | + |
| 175 | +```java |
| 176 | +heinz$ java --enable-preview --source 21 MethodsOnIterator.java |
| 177 | +public default void java.util.Iterator.remove() |
| 178 | +public default void java.util.Iterator.forEachRemaining(java.util.function.Consumer) |
| 179 | +public abstract boolean java.util.Iterator.hasNext() |
| 180 | +public abstract java.lang.Object java.util.Iterator.next() |
| 181 | +``` |
| 182 | + |
| 183 | +If we make an object of type `java.util.Iterator`, we would even be able to call these methods. In the next example, we look for the method |
| 184 | +called `"forEachRemaining"` and which takes a `Consumer` as a parameter. We then create an `Iterator` from a `List.of()` and |
| 185 | +invoke the `forEachRemaining` method using reflection. Note that |
| 186 | +several things could go wrong, most notably that the method does not |
| 187 | +exist (`NoSuchMethodException`) and that we are not allowed to call |
| 188 | +the method (`IllegalAccessException`). Since Java 7, we have a blanket |
| 189 | +exception that covers everything that can go wrong with reflection, |
| 190 | +the `ReflectiveOperationException`. |
| 191 | + |
| 192 | +```java |
| 193 | +// MethodsOnIteratorCalling.java |
| 194 | +import java.util.List; |
| 195 | +import java.util.Iterator; |
| 196 | +import java.util.function.Consumer; |
| 197 | + |
| 198 | +void main() throws ReflectiveOperationException { |
| 199 | + var iterator = List.of("Hello", "Dev", "Java").iterator(); |
| 200 | + var forEachRemainingMethod = Iterator.class.getMethod( |
| 201 | + "forEachRemaining", Consumer.class); |
| 202 | + Consumer<?> println = System.out::println; |
| 203 | + forEachRemainingMethod.invoke(iterator, println); |
| 204 | +} |
| 205 | +``` |
| 206 | + |
| 207 | +Our next example is even more interesting, if I may say so myself. |
| 208 | +We are going to take a `List` of items and then search through |
| 209 | +the `Collections` class to see whether we can find any methods |
| 210 | +that we can give the method to. We invoke the method and see what |
| 211 | +happens to our list. Since the methods are declared `static` in `Collections`, the first parameter of our `invoke()` method will |
| 212 | +be `null`. We could use a stream, but they don't "play nice" with |
| 213 | +checked exceptions, thus the plain old for-in loop it will have |
| 214 | +to be: |
| 215 | + |
| 216 | +```java |
| 217 | +// CollectionsListMethods.java |
| 218 | +import java.util.Collections; |
| 219 | +import java.util.List; |
| 220 | +import java.util.stream.Collectors; |
| 221 | + |
| 222 | +void main() throws ReflectiveOperationException { |
| 223 | + var pi = "3141592653589793".chars() |
| 224 | + .map(i -> i - '0') |
| 225 | + .boxed().collect(Collectors.toList()); |
| 226 | + System.out.println(pi); |
| 227 | + for (var method : Collections.class.getMethods()) { |
| 228 | + if (method.getReturnType() == void.class |
| 229 | + && method.getParameterCount() == 1 |
| 230 | + && method.getParameterTypes()[0] == List.class) { |
| 231 | + System.out.println("Calling " + method.getName() + "()"); |
| 232 | + method.invoke(null, pi); |
| 233 | + System.out.println(pi); |
| 234 | + } |
| 235 | + } |
| 236 | +} |
| 237 | +``` |
| 238 | + |
| 239 | +This works nicely and we find three methods that match our |
| 240 | +requirements: `sort()`, `shuffle()` and `reverse()`. The order of these |
| 241 | +methods is not guaranteed. For example, in the `Collections.java` |
| 242 | +file in OpenJDK 21, they are ordered as `sort()`, `reverse()`, |
| 243 | +`shuffle()`. However, when I run the code, they appear as: |
| 244 | + |
| 245 | +```output |
| 246 | +heinz$ java --enable-preview --source 21 CollectionsListMethods.java |
| 247 | +[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3] |
| 248 | +Calling reverse() |
| 249 | +[3, 9, 7, 9, 8, 5, 3, 5, 6, 2, 9, 5, 1, 4, 1, 3] |
| 250 | +Calling sort() |
| 251 | +[1, 1, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7, 8, 9, 9, 9] |
| 252 | +Calling shuffle() |
| 253 | +[5, 7, 4, 9, 9, 9, 2, 1, 6, 5, 3, 3, 1, 5, 3, 8] |
| 254 | +``` |
| 255 | + |
| 256 | +## Deep Reflective Access |
| 257 | + |
| 258 | +Up to now, we did not do anything that was particularly |
| 259 | +dangerous. All the methods we discovered and called were `public`. |
| 260 | +The only part that was a bit dangerous was that we did not have a |
| 261 | +compiler check that these methods exist and are accessible. |
| 262 | +However, we can also stare more deeply into the mirror. |
| 263 | + |
| 264 | +For example, let's consider our `Person` class: |
| 265 | + |
| 266 | +```java |
| 267 | +public class Person { |
| 268 | + private final String name; |
| 269 | + private final int age; |
| 270 | + |
| 271 | + public Person(String name, int age) { |
| 272 | + this.name = name; |
| 273 | + this.age = age; |
| 274 | + } |
| 275 | + |
| 276 | + public String toString() { |
| 277 | + return name + " (" + age + ")"; |
| 278 | + } |
| 279 | +} |
| 280 | +``` |
| 281 | + |
| 282 | +Since we are now working with two separate classes, we will need |
| 283 | +to compile them. We can use an unnamed class as before for the demo, |
| 284 | +but we should still compile them both. It would be a mistake to use a |
| 285 | +single-file call, because in that case `Person` and the demo would |
| 286 | +exist in different class loaders. This can cause hard to understand |
| 287 | +runtime errors. I once spent a day chasing this exact error. Don't |
| 288 | +be me. |
| 289 | + |
| 290 | +Here is our `FountainOfYouth.java` file: |
| 291 | + |
| 292 | +```java |
| 293 | +// FountainOfYouth.java |
| 294 | +import java.lang.reflect.*; |
| 295 | + |
| 296 | +void main() throws ReflectiveOperationException { |
| 297 | + var person = new Person("Heinz Kabutz", 51); |
| 298 | + System.out.println(person); |
| 299 | + Field ageField = Person.class.getDeclaredField("age"); |
| 300 | + ageField.setAccessible(true); // deep reflection engaged! |
| 301 | + int age = (int) ageField.get(person); |
| 302 | + age *= .9; |
| 303 | + ageField.set(person, age); |
| 304 | + System.out.println(person); |
| 305 | +} |
| 306 | +``` |
| 307 | + |
| 308 | +We first compile the `FountainOfYouth` class, which transitively |
| 309 | +compiles `Person.java`. We then run it, and *voila*, I've shaved |
| 310 | +10% off my age. |
| 311 | + |
| 312 | +```output |
| 313 | +heinz$ javac --enable-preview --source 21 FountainOfYouth.java |
| 314 | +heinz$ java --enable-preview FountainOfYouth |
| 315 | +Heinz Kabutz (51) |
| 316 | +Heinz Kabutz (45) |
| 317 | +``` |
| 318 | + |
| 319 | +Note that the `age` field is `private` *and* `final`, and yet we |
| 320 | +were able to change it. If we convert `Person` to a record, then |
| 321 | +it will no longer allow us to change the properties via deep |
| 322 | +reflection. |
| 323 | + |
| 324 | +### The Java Module System |
| 325 | + |
| 326 | +Deep reflection only works if the module in which the class resides |
| 327 | +is *open* to us. Ideally we should ask the author of the module |
| 328 | +to open the package to our module. They will likely refuse, and |
| 329 | +with good reason. By opening up their package, they allow |
| 330 | +unfettered access to their most intimate implementation detail. |
| 331 | +What if, in the near or distant future, they want to change |
| 332 | +field names or types? The deep reflective code would likely stop |
| 333 | +working, and they would be forever having to fix other modules. |
| 334 | + |
| 335 | +We can open up a package in another module for deep reflection |
| 336 | +with a command line parameter `--add-opens`, but we should only |
| 337 | +use that as an absolute last resort. It is so undesirable, that |
| 338 | +I merely mention it here in disgust, but won't show more details |
| 339 | +of how to use it. |
| 340 | + |
| 341 | +## Conclusion |
| 342 | + |
| 343 | +We hope that you got a bit of insight into how reflection works |
| 344 | +in this tutorial. There are many other topics to explore: arrays, |
| 345 | +dynamic proxies, generics, sealed classes, etc. How we can read |
| 346 | +the properties of a record, how parameter names can be preserved. |
| 347 | +But this is long enough and will hopefully get you started in the |
| 348 | +right direction. |
| 349 | + |
| 350 | +For many more deep dives into the Java Programming Language, be |
| 351 | +sure to subscribe to |
| 352 | +[The Java Specialists' Newsletter](https://www.javaspecialists.eu), |
| 353 | +a newsletter for anyone who wants to become more proficient in Java. |
0 commit comments