7

In Java you can "capture" a "method call on object" as a Runnable, as in belows example.

Later, having access to this instance of Runnable, is it possible to actually access the "captured" object and the method parameters of a method which is called (if possible this probably needs to be done via reflection).

For example:

class SomePrintingClass {
  public void print(String myText) {
    System.out.println(myText);

  }
}


public class HowToAccess {
  public static void main(String[] args) throws Exception {
    final String myText = "How to access this?";

    final SomePrintingClass printer = new SomePrintingClass();

    Runnable r = () -> printer.print(myText); // capture as Runnable

    inspect(r);
  }


  private static void inspect(Runnable runnable) {
    // I have reference only to runnable... can I access "printer" here

  }


}

Is it possible in the "inspect" method to access (probably via reflection) "printer" object and "myText" which was passed as a parameter?

Dr. Hans-Peter Störr
  • 25,298
  • 30
  • 102
  • 139
walkeros
  • 4,736
  • 4
  • 35
  • 47
  • 1
    Have you considered using a more specific interface than `Runnable`, with a method returning what you actually need? The type of reflection code you are about to use would probably not be very appropriate for production code, as you are enforcing a hidden requirement on the passed-in `Runnable` (i.e. being a lambda and having captured some variable). – Didier L Feb 17 '17 at 14:15
  • @DidierL: If you mean that `SomePrintingClass` could implement this "more specific" interface than I can not do this. Let's say I can not change anything in definition of `SomePrintingClass` and I can only rely on what I get in inspect() method. I may however use another interface than Runnable (create my own) - it is ok change the signature of `inspect` method, but this probably won't simplyfiy the problem. Concerning "hidden requirement" than I agree, but actually in this case it is ok to get high chance to access the object and the param. I don't need to be 100% sure. – walkeros Feb 17 '17 at 16:35
  • 1
    Yes I was talking about using something more specific than `Runnable`. If you do that, you don't need any reflection. – Didier L Feb 17 '17 at 20:18
  • The title of the question is inconsistent with the text: the title asks for "the object and the method", whereas the text asks about "the object and the method parameters", which is something quite different. What is actually meant? – Dr. Hans-Peter Störr May 19 '19 at 09:34
  • @Hans-PeterStörr: Having access to Method would grant access to its parameters. That's why I asked about method. It's just a title, though. The intent od the questions is described in the description. Accessing the parameter's value is the final intent, but if we went beyond this, it would be even better. – walkeros May 26 '19 at 14:51
  • @walkeros I don't see how finding out which method is called would give you access to the parameters. – Dr. Hans-Peter Störr May 29 '19 at 09:50

3 Answers3

10

It is possible, because the captured references are translated into fields of the runnable (as with all anonymous classes). The names will be not be consistent however.

I found by testing that you need to make myText non-final, otherwise it will be seen as a compile time constant and in-lined (and will not be accessible as a field):

private static void inspect(Runnable runnable) throws Exception {       
    for(Field f : runnable.getClass().getDeclaredFields()) {
        f.setAccessible(true);
        System.out.println("name: " + f.getName());
        Object o = f.get(runnable);
        System.out.println("value: " + o);
        System.out.println("class: " + o.getClass());
        System.out.println();
    }
}

Prints:

name: arg$1
value: test.SomePrintingClass@1fb3ebeb
class: class test.SomePrintingClass

name: arg$2
value: How to access this?
class: class java.lang.String
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • As I expected there must have been a way to do this:)... and thanks also for solving the "final" - "non-final" with a param. Accepted. Cheers. – walkeros Feb 17 '17 at 13:21
  • 2
    Well, either, non-`final`, or not immediately initialized, e.g. `final String myText; myText = "How to access this?";` is not a compile-time constant (though that’s not forbidding inlining, it’s just not happening with the current compiler, whereas for the compile-time constants, inlining is mandatory). – Holger Feb 17 '17 at 17:48
  • 6
    While it's interesting that it's possible to do this, it should be noted that this relies on implementation-specific behavior (not defined by specification) and thus it may break in different JDK releases. – Stuart Marks Feb 17 '17 at 21:15
  • 2
    In addition to relying on implementation-specific behavior, as Stuart reMarks, it should be obvious (but apparently it isn't) that you are accessing the private state of a class you don't control,which should have been your big red warning sign that you are doing something very wrong, and perhaps you should look for another solution. (It's also possible to break into people's houses and steal stuff, but just because you can, doesn't mean you should.) – Brian Goetz Feb 20 '17 at 00:43
0

With reflection, it is not possible to get local variables and method parameter values. Instead you can use AOP to intercept the method call and inspect the parameters.

Nektie
  • 94
  • 9
-2

Check if you want something as the below code where I have passed the runnable to a Thread object in your inspect method.

    class SomePrintingClass {
          public void print(String myText) {
            System.out.println(myText);

          }
        }


        public class HowToAccess {
          public static void main(String[] args) throws Exception {
            final String myText = "How to access this?";

            final SomePrintingClass printer = new SomePrintingClass();

            Runnable r = () -> printer.print(myText); // capture as Runnable

            inspect(r);
          }


          private static void inspect(Runnable runnable) {
            Thread t = new Thread(runnable);
            t.start();

          }


        }

output will be:

How to access this?

Arindam
  • 185
  • 2
  • 14