It’s seems you have missed an important point about the pass-by-reference concept: whenever a write into the reference happens, the referenced variable will be updated. This is different to any concept like yours that will actually pass a copy in a holder and update the original variable upon method return.
You can notice the difference even in single-threaded use case:
foo(myField, ()-> {
// if myField is pass-by-reference, whenever foo() modifies
// it and calls this Runnable, it should see the new value:
System.out.println(myField);
});
Of course, you could make both references accessing the same wrapper, but for an environment allowing (almost) arbitrary code, it would imply that you would have to replace every reference to the field (in the end, change the contents of the field) to the wrapper.
So if you want to implement a clean, real pass-by-value mechanism within the JVM, it must be able to modify the referenced artifact, i.e. field or array slot. For local variables, there is no way to do it so there’s no way around replacing local variables with a holder object once a reference to it has been created.
So the kind of options is already known, you can pass a java.lang.reflect.Field
(does not work with array slots), a pair of java.lang.invoke.MethodHandle
or an arbitrary typed object (of a generated type) offering read and write access.
When implementing this reference accessor type, you can resort to Unsafe
to create an anonymous class just like Java’s lambda expression facility does. If fact, you can steal inspire yourself a lot from the lambda expression mechanism:
- put an
invokedynamic
instruction at the place where a reference has to be created, pointing to your factory method and providing a handle to the field or array slot
- Let the factory analyze the handle and dynamically create the accessor implementation, the main difference being that your type will have two operations, read and write
- Use
Unsafe
to create that class (which might access the field, even if its private
)
- If the field is static, create an instance and return a
CallSite
with a handle returning that instance
- Otherwise return a
CallSite
with a handle pointing to the constructor of the accessor class accepting an object instance or an array
This way you will only have an overhead at the first-time usage while subsequent uses will either use singleton in the case of static
fields or construct an accessor on-the-fly for instance fields and array slots. These accessor instance creation can be elided by HotSpots escape analysis if used frequently just like with ordinary objects.