Oddly enough, I've been thinking about this problem myself recently. I was considering whether it might be fun to create a dialect of VB that ran on the JVM - I decided it wouldn't be.
Anyway, there are two main cases where this is likely to be useful and well defined:
- local variables
- object attributes
I'm assuming that you're writing a new compiler (or adapting an existing one) for your new dialect of Java.
Local variables are typically handled by code similar to what you're proposing. I'm most familiar with Scala, which doesn't support pass-by-reference, but does support closures, which have the same issues. In Scala, there's a class scala.runtime.ObjectRef
, which resembles your Holder
class. There are also similar {...}Ref
classes for primitives, volatile variables, and similar.
If the compiler needs to create a closure that updates a local variable, it "upgrades" the variable to a final ObjectRef
(which can be passed to the closure in its constructor), and replaces uses of that variable by get
s and updates by set
s, on the ObjectRef
. In your compiler, you could upgrade local variables whenever they're passed by reference.
You could use a similar trick with object attributes. Suppose that Holder
implements an interface ByRef
. When your compiler sees an object attribute being passed by reference, it could create an anonymous subclass of ByRef
that reads and updates the object attribute in its get
and set
methods. Again, Scala does something similar to this for lazily evaluated parameters (like references, but read-only).
For extra brownie points, you could extend the techique to JavaBean properties and even Map
, List
and Array
elements.
One side effect of this is that at the JVM level, your methods have unexpected signatures. If you compile a method with signature void doIt(ref String)
, at the bytecode level, you'll end up with the signature void doIt(ByRef)
(you might expect this to be something like void doIt(ByRef<String>)
, but of course generics use type erasure). This can cause problems with method overloading, as all by-ref parameters compile to the same signature.
It may be possible to do this with bytecode manipulation, but there are pitfalls, like the fact that the JVM permits applications to re-use local variables - so at the bytecode level, it may not be clear whether a parameter is being re-assigned, or its slot re-used, if the application was compiled without debugging symbols. Also, the compiler may elide aload
instructions if there's no possibility of a value having changed within the outer method - if you don't take steps to avoid this, changes to your reference variable may not be reflected in the outer method.