Skip to content

Commit 78a4ebc

Browse files
committed
Reflection article and author bio
1 parent 4ed0f16 commit 78a4ebc

File tree

2 files changed

+370
-0
lines changed

2 files changed

+370
-0
lines changed

app/data/authors.yaml

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

0 commit comments

Comments
 (0)