Skip to content

Commit 4c5f705

Browse files
authored
Merge pull request #35 from java/reflection-heinz
Reflection article and author bio
2 parents f764453 + 259d2ad commit 4c5f705

File tree

2 files changed

+365
-0
lines changed

2 files changed

+365
-0
lines changed

app/data/authors.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,15 @@
5757
website: https://horstmann.com
5858
description: |
5959
Cay Placeholder.
60+
61+
- name: Dr Heinz M. Kabutz
62+
email: heinz@javaspecialists.eu
63+
photo_url: https://javaspecialists.eu/pics/gb/heinz2-300x300.jpg
64+
github: kabutz
65+
twitter: kabutz
66+
website: https://www.javaspecialists.eu
67+
description: |
68+
Heinz is the author of [The Java Specialists' Newsletter](https://www.javaspecialists.eu),
69+
a monthly newsletter that has been in publication since the end of 2000. In it, he explores
70+
many useful tips and tricks that have enthralled tens of thousands of enthusiastic fans for
71+
over two decades. He was one of the first [Java Champions](https://www.javachampions.org).
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
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">&nbsp;</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

Comments
 (0)