7

How can one obtain objects of the following types from specific instances of their represented language feature in the code:

  • java.lang.reflect.Executable
  • java.lang.reflect.Parameter

Ideally the providing method would be a caller of the obtaining method or the obtaining method itself.

Note that I have found two kinds of answers on SO:

  • how to get a Parameter from a given Executable object (trivial!)
  • how to get the name (not signature!) of a method from the call stack

Neither helps to obtain the Parameter to a particular method whose name is not unique.

Consider in particular how this answer and this one to similar questions gloss over the selection of the desired method.

Now, there exists Class.getMethod(String, Class<?>... types) but it does not seem that one could generate the "types" parameter automatically from an existing method definition?

Use case:

public class MyAssertions
{
  private static final String DEFAULT_MESSAGE_NOT_NULL = "Parameter must not be null: ";

  /* Ideal. */
  public static void notNull(Object ref, Parameter param)
  {
    if(ref == null)
      throw new IllegalArgumentException(DEFAULT_MESSAGE_NOT_NULL + param.getName());
  }

  /* Still okay. */
  public static void notNull(Object ref, Executable caller, int param)
  {
    if(ref == null)
      throw new IllegalArgumentException(DEFAULT_MESSAGE_NOT_NULL
                                       + caller.getParameters()[param].getName());
  }

  /* Hell no! */
  public static void notNull(Object ref, Class<?> caller, String method, Object[] paramTypes, int param)
  {
    if(ref == null)
      throw new IllegalArgumentException(DEFAULT_MESSAGE_NOT_NULL
                                       + caller.getMethod(method, paramTypes)
                                         .getParameters()[param].getName());
  }
}
4
  • 3
    is it just me or you need to provide an example of what you want to achieve?
    – Eugene
    Commented Jun 15, 2017 at 9:25
  • 5
    I’m afraid, this is impossible. On the other hand, if you are inside the method, you already know the method, its signature and so on. Using this information directly hinders copy&paste programming or Refactoring, but these should not be your main development styles anyway. —Oh well, if you could do this for the caller, it would perhaps enable developing some neat tools, however, it is not supported by the standard API, not even Java 9’s stack walker…
    – Holger
    Commented Jun 15, 2017 at 9:48
  • @Eugene : Here you go.
    – Zsar
    Commented Jun 15, 2017 at 10:08
  • 1
    The use case doesn’t cover the actual use case, i.e. the potential caller(s) of these methods. There is a trick, how callers could get to their own Method object if performance doesn’t matter, but if all you want, is the parameter name, and you’re even introducing a dependency to the parameter position at the same time, it doesn’t make much sense. Isn’t Objects.requireNonNull(myParam, "myParam") cleaner and simpler than MyAssertions.notNull(myParam, 42)? Especially, as the latter only provides a name if the method has been compiled with a special option to retain method parameter names?
    – Holger
    Commented May 23, 2022 at 15:56

1 Answer 1

1

You can use bytecode utilities to get the bytecode infomation. And then use the line number info in StackTraceElement to get the method.

Here I use javassist.

  private static Optional<Method> get(StackTraceElement e) throws NotFoundException, ClassNotFoundException {
    Class<?> clz = Class.forName(e.getClassName());
    int line = e.getLineNumber();
    ClassPool pool = ClassPool.getDefault();
    CtClass cc = pool.get(clz.getName());
    return Observable.fromArray(cc.getDeclaredMethods())
        .sorted(Comparator.comparing(m -> m.getMethodInfo().getLineNumber(0)))
        .filter(m -> m.getMethodInfo().getLineNumber(0) <= line)
        .map(Optional::of)
        .blockingLast(Optional.empty())
        .map(m -> uncheck(() -> clz.getDeclaredMethod(m.getName(),
            Arrays.stream(m.getParameterTypes()).map(c -> uncheck(() -> nameToClass(c.getName()))).toArray(Class[]::new))));
  }

  private static Class<?> nameToClass(String name) throws ClassNotFoundException {
    switch (name) {
    case "int":
      return int.class;
    case "short":
      return short.class;
    case "long":
      return long.class;
    case "double":
      return double.class;
    case "float":
      return float.class;
    case "boolean":
      return boolean.class;
    case "char":
      return char.class;
    case "byte":
      return byte.class;
    case "void":
      return void.class;
    }
    if (name.endsWith("[]")) {
      return Array.newInstance(nameToClass(name.substring(0, name.length() - 2)), 0).getClass();
    }
    return Class.forName(name);
  }

uncheck is my utility only ignore the exception in lambda, you can simply use try-catch. see source here

then we test our code

  public static void main(String[] args) throws Exception {
    // test normal
    System.out.println(get(new Exception().getStackTrace()[0]));
    // test lambda
    Runnable r = () -> System.out.println(uncheck(() -> get(new Exception().getStackTrace()[0])));
    r.run();
    // test function
    testHere(1);
  }

  private static void testHere(int i) throws Exception {
    System.out.println(get(new Exception().getStackTrace()[0]));
  }

and output

Optional[public static void xdean.stackoverflow.java.reflection.Q44563354.main(java.lang.String[]) throws java.lang.Exception]
Optional[private static java.util.Optional xdean.stackoverflow.java.reflection.Q44563354.lambda$1() throws java.lang.Exception]
Optional[private static void xdean.stackoverflow.java.reflection.Q44563354.testHere(int) throws java.lang.Exception]

But note that this approach only works for class has bytecode in classpath. If you use dynamic proxy, it doesn't work.

Find complete sample code here

EDIT: You can filter synthetic and bridge methods to get the actual method.

Not the answer you're looking for? Browse other questions tagged or ask your own question.