August 7th, 2008

Nifty - java BYREF passing.

I have to give Roel credit for this one, he built something like this @ his workplace at some point, but it’s still nifty. Also, it crashes about half the javac compilers out there, but at least on my local version of eclipse this runs fine and produces the expected output (10, 11, 12, 13):

public final class Reference<T> {
	public T ref;

	private Reference(T initialVal) {
		ref = initialVal;
	}

	public static <T> Reference<T> reference(T initialVal) {
		return new Reference(initialVal);
	}

	public static void main(String[] args) {
		Reference r = create(10);
		System.out.println(r.ref);
		r.ref++; //A
		System.out.println(r.ref);
		r.ref += 1; //B
		System.out.println(r.ref);
		r.ref = r.ref + 1; //C
		System.out.println(r.ref);
	}
}

The crazy stunts you can pull with generics. Depending on your compiler, either this works, or line ‘A’ causes javac to crash completely, B produces a compile error, and C compiles on all versions.

This device is actually useful because it can replace, completely, every instance where ordinarily you’d use the ‘array of size 1′ hack. This occurs in two unrelated situations:

Pass-by-reference

Let’s say I have an int variable in a method. Call it ‘foobar’. I want to call some method, and pass it not the VALUE of this int, but the object, so that the method I call can change it. OO code style cries everytime you try this, but let’s say you -really- want to. You can’t use ‘int’, obviously, because primitives always pass by value. You can try ‘Integer’ instead, but those are immutable - still can’t be changed. The usual solution is to use a new int[1].

From now on you can use a Reference<Integer>:

Reference<Integer> r = reference(25);
call(r);
System.out.println(r.ref); //prints 30.

public void call(Reference<Integer> r) {
    r.ref = 30;
}

Modifying parent scope variables from inside an inner class

The following snippet won’t compile, because any variables accessed inside an on-the-fly written class must be ‘final’:

int x = 10;
new Runnable() { public void run() { x = 12;}}

The array-of-size-1 trick works here again, but Reference looks better:

final Reference<Integer> x = reference(10);
new Runnable() { public void run() { x.ref = 12;}}

(Note, with CICE, ‘the final’ would be inferred due to x being used in an inner class).

Some even crazier stuff with this thing after the jump, and a puzzler!

We can flesh out this Reference thing by adding appropriate hashCode, equals, and toString methods. However, before we consider doing that, what would happen if we create a reference that refers to itself? Something like Reference r = reference(null); r.ref = r;?

Well, if we simply link to the contained object for those 3 methods, calling any of them would result in an endless loop. No good. We can easily safe up our code by writing explicit checks for that condition in those methods.

You can download a full version here.

and now for the puzzler: The above code sample:

Reference r = reference(null);
r.ref = r;

compiles but causes some generics warnings. Can you construct a version that compiles to the exact same thing without the warnings (and using @SupressWarnings is obviously not allowed!)?

Second puzzler: The code linked has some safety checks inside that prevent endless loops if you refer a reference to itself. Is there a way you can fake it out and generate an endless loop anyway?

4 Responses to 'Nifty - java BYREF passing.'

  1. 1Roel Spilker
    December 5th, 2006 at 10:04

    Reinier,

    The line if ( o == null ) return true; should be removed. According to the contract of equals: “For any non-null reference value x, x.equals(null) should return false

    The line if ( ref == this ) return (o instanceof Reference && ((Reference)o).ref == o); breaks the contract of hashCode. It causes the equals to return true if both this object and the other object are referencing themselves. However, on this line they are never compared against each other. Since the hashcode depends on the hashcode of ref, two references can be “equal” to each other, but have different hashcodes. The contract for hashCode is: “If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.”


  2. 2Roel Spilker
    December 5th, 2006 at 10:11

    Finally, the line else return ref.equals(o); also breaks the contract of equals. The contract states: “It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.”
    If I create a Reference to a String, calling equals with the same string will return true and calling the equals of the string with the reference will return false.


  3. 3Roel Spilker
    December 5th, 2006 at 11:41

    Hmm. My second remark is invalid. Reinier adjusted hashCode accordingly. This is valid because every reference to itself doesn’t contain any other information.


  4. 4rzwitserloot
    December 5th, 2006 at 15:55

    The equals/hashCode contract has been broken before. Due to the mutable nature of References, using them as keys in HashMaps and the like is not something that’ll ever work right. In this case there is no sensible implementation of equals/hashCode available, I think.


Leave a Response

(Note: if you use a new name from an unknown ip address, your comment won't appear until I approve it. Anti-spam measure only, I don't censor).

Imhotep theme designed by Chris Lin. Proudly powered by Wordpress.
XHTML | CSS | RSS | Comments RSS