How can you extend Java to introduce passing by reference?

前端 未结 10 2198
天命终不由人
天命终不由人 2020-12-24 06:19

Java is pass-by-value. How could you modify the language to introduce passing by reference (or some equivalent behavior)?

Take for example something like

<         


        
相关标签:
10条回答
  • 2020-12-24 06:58

    The usual idiom I've seen for pass-by-reference in Java is to pass a single-element array, which will both preserve run-time type-safety (unlike generics which undergo erasure) and avoid the need to introduce a new class.

    public static void main(String[] args) {
        String[] holder = new String[1];
    
        // variable optimized away as holder[0]
        holder[0] = "'previous String reference'";
    
        passByReference(holder);
        System.out.println(holder[0]);
    }
    
    public static void passByReference(String[] someString) {
        someString[0] = "'new String reference'";
    }
    
    0 讨论(0)
  • 2020-12-24 07:06

    Java is (in fact) pass by reference. When the method is called, the reference(pointer) to the object is passed and when you modify the object you can see the modification when you return from the method. The problem with your example is that java.lang.String is immutable.

    And what you are actually achieving with your example is output parameters.

    Here is a slightly different version of Jeffrey Hantin:

    public static void main(String[] args) {
      StringBuilder variable = new StringBuilder("'previous String reference'");
      passByReference(variable);
      System.out.println(variable); // I want this to print 'new String reference'
    }
    
    public static void passByReference(StringBuilder someString) {
      String nr = "'new String reference'";
      someString.replace(0, nr.length() - 1, nr);
    }
    
    0 讨论(0)
  • 2020-12-24 07:07

    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 gets and updates by sets, 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.

    0 讨论(0)
  • 2020-12-24 07:11

    There are several ways to write Java code as effectively pass-by-reference, even within the standard pass-by-value conventions.

    One approach is to use instance or static variables whose scope includes a particular method, in lieu of explicit parameters. The variables which are being modified could be included in the comments, if you really want to mention their names at the beginning of a method.

    The disadvantage with this approach is that the scope of these variables would need to encompass the entire class in question, rather than only the method. If you would like to restrict the variables' scopes more precisely, you could always modify them using getter and setter methods rather than as parameters.

    Having worked with both Java and C/C++, I don't think Java's supposed inflexibility in being pass-by-value only is a big deal--for any programmers who know what happens to the variables, there are reasonable workarounds that can accomplish the same things functionally.

    0 讨论(0)
  • 2020-12-24 07:12

    Your attempt to modify the language ignores the fact that this "feature" was explicitly left out to prevent well-known side-effect bugs from being able to happen in the first place. Java recommends to do what you are trying to archive by the use of data-holder classes:

    public class Holder<T> {
      protected T value;
    
      public T getValue() {
        return value;
      }
    
      public void setValue(T value) {
        this.value = value;
      }
    }
    

    A thread-safe version would be the AtomicReference.

    Now storing a single String in a class seems over-kill and most likely it is, however usually you have a data-holder class for several related values instead of a single String.

    The big benefit of this approach is that what happens inside the method is very explicit. So even if you are programming on a Monday morning after an eventful weekend and the coffee machine just broke down, you still can tell easily what the code is doing (KISS), preventing several bugs from even happening in the first place, just because you forgot about that one feature of method foo.

    If you think about what your approach can do that the data-holder version cannot, you'll soon realize that you are implementing something just because it is different, but effectively it has no real value.

    0 讨论(0)
  • 2020-12-24 07:15

    Using AtomicReference class as holder object.

    public static void main(String[] args) {
        String variable="old";
        AtomicReference<String> at=new AtomicReference<String>(variable);
        passByReference(at);
        variable=at.get();
        System.out.println(variable);
    }
    
    public static void passByReference(AtomicReference<String> at) {
      at.set("new");
    }
    
    0 讨论(0)
提交回复
热议问题