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".