Problem #

Inspired by a recent post on easily obtaining method parameter name by identifier, here is a technique to obtain a method’s name by method reference.

Imagine you have a class with getters that follow typical Java Bean naming convention or Java Record naming convention:

class Book {
   ...
   public String getAuthor() { ... }
   public String title() { ... }
}

How can we easily obtain "title" or "author" strings? Perhaps you want to directly parse CSV content, database results, or call a REST API:

String title = csvContents.get(nameOf(Book::title));
String author = dbQueryResults.get(beanNameOf(Book::getAuthor));
List<Book> books = bookClient.findByAttr(singletonMap(nameOf(Book::getAuthor), "Shakespeare"));

Solution #

We can implement such a nameOf(...) functionality in Java 8+ by serializing a method reference to SerializedLambda which lets us easily read the method’s name:

  • Declare a serializable getter functional interface: interface Getter<T, R> extends Function<T, R>, Serializable {}
  • When serializing it, Java will create a SerializedLambda as a replacement object.
  • Intercept that SerializedLambda object and read the method’s name

Here is the code:

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.util.function.Function;

public class GetterTool {

    @FunctionalInterface
    public interface Getter<T, R> extends Function<T, R>, Serializable {}

    public static <T, R> String nameOf(final Getter<T, R> methodRef) {
        return toSerializedLambda(methodRef).getImplMethodName();
    }

    private static <T, R> SerializedLambda toSerializedLambda(final Object methodRef) {
        try {
            try (final CustomObjectOutputStream stream = new CustomObjectOutputStream()) {
                stream.writeObject(methodRef);
                return (SerializedLambda) stream.interceptedObject;
            }
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static class CustomObjectOutputStream extends ObjectOutputStream {
        private Object interceptedObject = null;

        public CustomObjectOutputStream() throws IOException {
            super(nullOutputStream());
            enableReplaceObject(true);
        }

        @Override
        protected Object replaceObject(final Object obj) {
            if (this.interceptedObject == null) {
                this.interceptedObject = obj;
            }
            return obj;
        }

        private static OutputStream nullOutputStream() {
            return new OutputStream() {
                @Override public void write(final int b) { }
                @Override public void write(final byte[] b, final int off, final int len) { }
                @Override public void close() { }
            };
        }
    }
}

And we can use it like this:

@Test
void test() {
    assertEquals("title", GetterTool.nameOf(Book::title));
}

After implementing nameOf(), it’s straightforward to implement further transformations like beanNameOf() to remove the get prefix.

Other methods #

So this technique works for zero-argument getter methods but what if we want to obtain the name of other methods like:

List<Book> findByAttr(Map<String, Object> attr);
Book save(String title, String author);

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:

// for methods like findByAttr(...)
public interface Function2<T1, T2, R> extends Serializable {
    R apply(T1 t1, T2 t2);
}

// for methods like save(...)
public interface Function3<T1, T2, T3, R> extends Serializable {
    R apply(T1 t1, T2 t2, T3 t3);
}

<T1, T2, R> String nameOf(final Function2<T1, T2, R> methodRef);
<T1, T2, T3, R> String nameOf(final Function3<T1, T2, T3, R> methodRef);

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:

Method m1 = toMethod(BookClient::save);
Method m2 = toMethod(BookClient::findByAttr);

Take a look at this library: https://github.com/Hervian/safety-mirror/tree/master#type-safe-method-creation