Java Reference Semantics

Primitive values (int, float, char, etc.) are passed by value:

class Clone
{
   public static void swap(int x, int y)
   { // only local copies are swapped!
      int temp = x;
      x = y;
      y = temp;
   }

   public static void main(String[] args)
   {
      int x = 42, y = 53;
      System.out.println("x = " + x + ", y = " + y);
      System.out.println("calling swap(x, y)");
      swap(x, y);
      System.out.println("x = " + x + ", y = " + y);
   }
}

Program output: x = 42, y = 53

calling swap(x, y)

x = 42, y = 53

Objects (arrays and class instances) are passed and copied by reference:

Engines

class Engine
{
   public int rpm; // engine speed in rotations/minute
   public Engine() { rpm = 0; }
   public void accelerate() { rpm = Math.min(7000, rpm + 10); }
   public void decelerate() { rpm = Math.max(0, rpm - 10); }
   public void show() { System.out.println("rpm = " + rpm); }
} // Engine
Transmissions class Trans
{
   public int gear; // current gear (1, 2, 3, 4)
   public Trans() { gear = 1; }
   public void upshift() { gear = Math.min(4, gear + 1); }
   public void downshift() { gear = Math.max(1, gear - 1); }
   public void show() { System.out.println("gear = " + gear); }
} // Trans
Cars class Car
{
   public Engine engine;
   public Trans trans;
   private int speed; // in mph

   public Car()
   {
      trans = new Trans();
      engine = new Engine();
      speed = 0;
   }

   public void setSpeed() // depends on gear & engine speed
   {
      speed = trans.gear * engine.rpm/250;
   }

   public void show()
   {
      engine.show();
      trans.show();
      System.out.println("speed = " + speed);
   }
} // Car

Test Harness class Clone
{
   public static void drive(Car c)
   {
      c.engine.accelerate();
      c.engine.accelerate();
      c.engine.accelerate();
      c.trans.upshift();
      c.setSpeed();
      c.engine.accelerate();
      c.engine.accelerate();
      c.engine.accelerate();
      c.trans.upshift();
      c.setSpeed();
   }

   public static void main(String[] args)
   {
      Car car1, car2;
      car1 = new Car();
      car2 = car1; // car2 aliases car1
      System.out.println("car1:");
      car1.show();
      System.out.println("car2:");
      car2.show();
      drive(car1);
      System.out.println("car1:");
      car1.show();
      System.out.println("car2:");
      car2.show();
   }
} // Clone

Program Output: car1:
rpm = 0
gear = 1
speed = 0
car2:
rpm = 0
gear = 1
speed = 0
car1:
rpm = 6000
gear = 3
speed = 72
car2:
rpm = 6000
gear = 3
speed = 72
Object Diagram:

Note: Java reference semantics is similar to Scheme reference semantics.

Cloning Objects

Let the cloning begin!

The Object base class provides a protected cloning method:

class Object
{
   protected Object clone() throws CloneNotSupportedException
   {
      // returns a bitwise copy of this
   }

   etc.

} // Object

A derived class can override this method with a public version that calls super.clone(). A class that does this is called cloneable (sic.). We can flag a class as cloneable by declaring that it implements the empty Cloneable interface: interface Cloneable {} Making Shallow Copies

Cars

class Car implements Cloneable
{
   public Object clone()
   {
      Object obj = null;
      try
      {
         obj = super.clone(); // allocate memory & make bitwise shallow copy
      }
      catch(CloneNotSupportedException e)
      {
         System.err.println("Can't clone cars!");
      }
      return obj;
   }

   // etc.

} // Car

Test Harness class Clone
{
   // etc.

   public static void main(String[] args)
   {
      Car car1, car2;
      car1 = new Car();
      if (car1 instanceof Cloneable) // Car implements Cloneable?
         car2 = (Car)car1.clone();
      else
         car2 = car1;
      System.out.println("car1:");
      car1.show();
      System.out.println("car2:");
      car2.show();
      drive(car1);
      System.out.println("car1:");
      car1.show();
      System.out.println("car2:");
      car2.show();
   }
} // Clone

Program Output car1:
rpm = 0
gear = 1
speed = 0
car2:
rpm = 0
gear = 1
speed = 0
car1:
rpm = 6000
gear = 3
speed = 72
car2:
rpm = 6000
gear = 3
speed = 0
Object Diagram

Making Deep Copies

Engines

class Engine implements Cloneable
{

   public Object clone()
   {
      Object obj = null;
      try
      {
         obj = super.clone(); // allocate memory & make bitwise shallow copy
      }
      catch(CloneNotSupportedException e)
      {
         System.err.println("Can't clone engines!");
      }
      return obj;
   }

   // etc.

} // Engine

Transmissions // Transmission
class Trans implements Cloneable
{
   public Object clone()
   {
      Object obj = null;
      try
      {
         obj = super.clone(); // allocate memory & make bitwise shallow copy
      }
      catch(CloneNotSupportedException e)
      {
         System.err.println("Can't clone transmissions!");
      }
      return obj;
   }

   // etc.

} // Trans

Cars class Car implements Cloneable
{
   public Object clone()
   {
      Car obj = null;
      try
      {
         obj = (Car) super.clone(); // allocate memory & make bitwise shallow copy
      }
      catch(CloneNotSupportedException e)
      {
         System.err.println("Can't clone cars!");
      }
      // clone engine & transmission:
      if (engine instanceof Cloneable) // Engine implements Cloneable?
         obj.engine = (Engine)engine.clone();
      if (trans instanceof Cloneable) // Trans implements Cloneable?
         obj.trans = (Trans)trans.clone();
      return obj; // upcasts to an Object
   }

   // etc.

} // Car

Output of Clone.main(): car1:
rpm = 0
gear = 1
speed = 0
car2:
rpm = 0
gear = 1
speed = 0
car1:
rpm = 6000
gear = 3
speed = 72
car2:
rpm = 0
gear = 1
speed = 0
Object Diagram
Immutable Objects

Although reference semantics is efficient, it can lead to aliasing problems. For example:

car2 = car1; // car2 an alias for car1
car2.engine = null; // car2 lost its engine
car1.engine.accelerate(); // error, car1 lost its engine, too!
Java programmers are responsible for preventing aliasing problems by implementing and calling clone().

Reference semanitcs can't cause aliasing problems if objects are immutable. An object is immutable or stateless if it can't be altered, i.e., if it is read-only. Otherwise we say an object is mutable.

It's easy to create immutable classes, just make all member variables private, and don't provide any mutators, i.e., methods that modify the member variables. Integer, Double, and String are examples of pre-defined immutable classes. StringBuffer is the mutable analog of String:

String s = "cat";
StringBuffer buff = new StringBuffer(s);
buff.setCharAt(0, 'b');
String t = buff.toString();
System.out.println("s = " + s + ", t = " + t); // prints s = cat, t = bat
This can be inefficient. To change "cat" to "bat" we had to duplicate "at".