Passing handles around

Bruce Eckel’s Thinking in Java Contents | Prev | Next

//: PassHandles.java
// Passing handles around
package c12;
 
public class PassHandles {
  static void f(PassHandles h) {
    System.out.println("h inside f(): " + h);
  }
  public static void main(String[] args) {
    PassHandles p = new PassHandles();
    System.out.println("p inside main(): " + p);
    f(p);
  }
} ///:~ 

The
method
toString( )
is automatically invoked in the print statements, and
PassHandles
inherits directly from
Object
with no redefinition of
toString( ).
Thus,
Object’s
version of
toString( )
is
used, which prints out the class of the object followed by the address where
that object is located (not the handle, but the actual object storage). The
output looks like this:

p inside main(): [email protected]1653748
h inside f(): [email protected]1653748

You
can see that both
p
and
h
refer to the same object. This is far more efficient than duplicating a new
PassHandles
object just so that you can send an argument to a method. But it brings up an
important issue.

Aliasing

Aliasing
means that more than one handle is tied to the same object, as in the above
example. The problem with aliasing occurs when someone
writes
to that object. If the owners of the other handles aren’t expecting that
object to change, they’ll be surprised. This can be demonstrated with a
simple example:

//: Alias1.java
// Aliasing two handles to one object
 
public class Alias1 {
  int i;
  Alias1(int ii) { i = ii; }
  public static void main(String[] args) {
    Alias1 x = new Alias1(7);
    Alias1 y = x; // Assign the handle
    System.out.println("x: " + x.i);
    System.out.println("y: " + y.i);
    System.out.println("Incrementing x");
    x.i++;
    System.out.println("x: " + x.i);
    System.out.println("y: " + y.i);
  }
} ///:~ 

In
the line:

Alias1
y = x; // Assign the handle

a
new
Alias1
handle is created, but instead of being assigned to a fresh object created with
new,
it’s assigned to an existing handle. So the contents of handle
x,
which is the address of the object
x
is pointing to, is assigned to
y,
and thus both
x
and
y
are attached to the same object. So when
x’s
i
is incremented in the statement:

x.i++;

y’s
i
will
be affected as well. This can be seen in the output:

x: 7
y: 7
Incrementing x
x: 8
y: 8

One
good solution in this case is to simply not do it: don’t consciously
alias more than one handle to an object at the same scope. Your code will be
much easier to understand and debug. However, when you’re passing a
handle in as an argument – which is the way Java is supposed to work
– you automatically alias because the local handle that’s created
can modify the “outside object” (the object that was created
outside the scope of the method). Here’s an example:

//: Alias2.java
// Method calls implicitly alias their
// arguments.
 
public class Alias2 {
  int i;
  Alias2(int ii) { i = ii; }
  static void f(Alias2 handle) {
    handle.i++;
  }
  public static void main(String[] args) {
    Alias2 x = new Alias2(7);
    System.out.println("x: " + x.i);
    System.out.println("Calling f(x)");
    f(x);
    System.out.println("x: " + x.i);
  }
} ///:~ 

The
output is:

x: 7
Calling f(x)
x: 8

The
method is changing its argument, the outside object. When this kind of
situation arises, you must decide whether it makes sense, whether the user
expects it, and whether it’s going to cause problems.

In
general, you call a method in order to produce a return value and/or a change
of state in the object
that
the method is called for
.
(A method is how you “send a message” to that object.) It’s
much less common to call a method in order to manipulate its arguments; this is
referred to as “calling a method for its side
effects
.”
Thus, when you create a method that modifies its arguments the user must be
clearly instructed and warned about the use of that method and its potential
surprises. Because of the confusion and pitfalls, it’s much better to
avoid changing the argument.

More by Author

Must Read