Skip to content

Commit c32260b

Browse files
committed
Hacking the method name
1 parent c9e353c commit c32260b

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
---
2+
layout: post
3+
title: "Hacking the Method Name"
4+
---
5+
6+
# Problem
7+
8+
Inspired by a [recent post](https://committing-crimes.com/articles/2026-08-04-java-nameof/) on easily obtaining method parameter name by identifier, here is a technique to obtain a method's name by method reference.
9+
10+
11+
Imagine you have a class with getters that follow typical Java Bean naming convention or Java Record naming convention:
12+
13+
```java
14+
class Book {
15+
...
16+
public String getAuthor() { ... }
17+
public String title() { ... }
18+
}
19+
```
20+
21+
How can we easily obtain `"title"` or `"author"` strings? Perhaps you want to directly parse CSV content, database results, or call a REST API:
22+
23+
```java
24+
String title = csvContents.get(nameOf(Book::title));
25+
String author = dbQueryResults.get(beanNameOf(Book::getAuthor));
26+
List<Book> books = bookClient.findByAttr(singletonMap(nameOf(Book::getAuthor), "Shakespeare"));
27+
```
28+
29+
30+
# Solution
31+
32+
We can implement such a `nameOf(...)` functionality in Java 8+ by serializing a method reference to [SerializedLambda](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/SerializedLambda.html) which lets us easily read the method's name:
33+
34+
* Declare a serializable getter functional interface: `interface Getter<T, R> extends Function<T, R>, Serializable {}`
35+
* When serializing it, Java will create a SerializedLambda as a replacement object.
36+
* Intercept that SerializedLambda object and read the method's name
37+
38+
Here is the code:
39+
40+
```java
41+
import java.io.IOException;
42+
import java.io.ObjectOutputStream;
43+
import java.io.OutputStream;
44+
import java.io.Serializable;
45+
import java.lang.invoke.SerializedLambda;
46+
import java.util.function.Function;
47+
48+
public class GetterTool {
49+
50+
@FunctionalInterface
51+
public interface Getter<T, R> extends Function<T, R>, Serializable {}
52+
53+
public static <T, R> String nameOf(final Getter<T, R> methodRef) {
54+
return toSerializedLambda(methodRef).getImplMethodName();
55+
}
56+
57+
private static <T, R> SerializedLambda toSerializedLambda(final Object methodRef) {
58+
try {
59+
try (final CustomObjectOutputStream stream = new CustomObjectOutputStream()) {
60+
stream.writeObject(methodRef);
61+
return (SerializedLambda) stream.interceptedObject;
62+
}
63+
} catch (final IOException e) {
64+
throw new RuntimeException(e);
65+
}
66+
}
67+
68+
private static class CustomObjectOutputStream extends ObjectOutputStream {
69+
private Object interceptedObject = null;
70+
71+
public CustomObjectOutputStream() throws IOException {
72+
super(nullOutputStream());
73+
enableReplaceObject(true);
74+
}
75+
76+
@Override
77+
protected Object replaceObject(final Object obj) {
78+
if (this.interceptedObject == null) {
79+
this.interceptedObject = obj;
80+
}
81+
return obj;
82+
}
83+
84+
private static OutputStream nullOutputStream() {
85+
return new OutputStream() {
86+
@Override public void write(final int b) { }
87+
@Override public void write(final byte[] b, final int off, final int len) { }
88+
@Override public void close() { }
89+
};
90+
}
91+
}
92+
}
93+
```
94+
95+
And we can use it like this:
96+
97+
```java
98+
@Test
99+
void test() {
100+
assertEquals("title", GetterTool.nameOf(Book::title));
101+
}
102+
```
103+
104+
After implementing `nameOf()`, it's straightforward to implement further transformations like `beanNameOf()` to remove the `get` prefix.
105+
106+
107+
# Other methods
108+
109+
110+
So this technique works for zero-argument getter methods but what if we want to obtain the name of other methods like:
111+
112+
```
113+
List<Book> findByAttr(Map<String, Object> attr);
114+
Book save(String title, String author);
115+
```
116+
117+
Method references need to target a functional interface, so we would need to define corresponding serializable functional interfaces. Only then overloaded `nameOf()` methods can be defined and called:
118+
119+
```java
120+
// for methods like findByAttr(...)
121+
public interface Function2<T1, T2, R> extends Serializable {
122+
R apply(T1 t1, T2 t2);
123+
}
124+
125+
// for methods like save(...)
126+
public interface Function3<T1, T2, T3, R> extends Serializable {
127+
R apply(T1 t1, T2 t2, T3 t3);
128+
}
129+
130+
<T1, T2, R> String nameOf(final Function2<T1, T2, R> methodRef);
131+
<T1, T2, T3, R> String nameOf(final Function3<T1, T2, T3, R> methodRef);
132+
```
133+
134+
Very tedious! Fortunately, someone has done the tedious work and in fact made it better. We can easily obtain, not only the method name, but also the actual `java.lang.reflect.Method`:
135+
136+
```java
137+
Method m1 = toMethod(BookClient::save);
138+
Method m2 = toMethod(BookClient::findByAttr);
139+
```
140+
141+
Take a look at this library: [https://github.com/Hervian/safety-mirror/tree/master#type-safe-method-creation](https://github.com/Hervian/safety-mirror/tree/master#type-safe-method-creation)

0 commit comments

Comments
 (0)