javareflectioninstanceouter-classes

Java 21: Reflection: Access instance of outer class


I have a framework that needs to discover the instance for an outer class when supplied an instance of the inner class so that I can set a field of the outer instance.

class Outer {

  String outerField;
 
  class Inner {
    // String outerMaskedField() {
    //   return Outer.this.outerField;
    // }
  }
}

In Java 17 this was possible, because the inner class had a private field named "this$0" on the inner class. It was then possible to read that field by filtering for a field that had the type of the outer class (assuming that there is only 1).

Arrays.stream(target.getClass().getDeclaredFields())
    .filter(it -> it.getType() == target.getClass().getEnclosingClass())
    .findFirst();

However, in Java 21 this is not available anymore. Is there an alternative way to find this out at runtime?

As discussed in the comments, uncommenting the code will fix the error, but it is an undocumented solution. A less flaky solution would be desirable.


Solution

  • Java does not guarantee that the outer instance exists. Hence, what you want is impossible: Trivially there isn't a 'spec' based way to retrieve an object the spec doesn't guarantee in the first place.

    To be specific, this is what you want:

    Thus, either:

    In certain ways what you want is fundamentally confused. Java-esque coding does not as a rule care about this kind of thing so it's "weird" to write code that does, and it shouldn't be needed. A trivial example that is a much better solution for certain X (absolutely no idea if it is a good solution for your X, of course, it's just an example!):

    Simply declare an interface. Any instance of a class that implements it is suitable; the interface dictates there is a method named outerThis() (or, you should probably come up with a better name than that, generally you should name things after what they mean, not after a suggested implementation). It could be generic or not. If it's generic, you might even just want to go with Producer<T>, though, I'd think twice before doing that.

    Alternatively, declare a separate interface in a more factory style idea: This interface is like a Function<Inner, Outer> - as parameter you provide an inner, and this method returns the outer. Then any code can use your framework and all they have to do, is supply this function, which is generally a very short one-liner.

    Existing code currently based on 'we shall retrieve the outer this' can be adapted quite easily - these inner classes simply need to do 2 things to 'update':

    This cannot be done backwards compatible, but your "spec" is fundamentally wrong - you cannot spec "you must pass an instance of a non-static inner class; operations will be done on the outer instance" because java-the-lang-spec doesn't give you any guarantees about this. Given that your code was always broken, just, now it has started to 'hurt' due to changing internals, the best option is surely to accept, and break backwards compatibility on your own library to fix the oversight.

    This happens all the time! It has a name: Hyrum's Law and even an xkcd comic which surely counts as the gold standard:

    [Workflow]