This is a set of "slides" to accompany chapter 11 of Timothy Budd's textbook Understanding Object-Oriented Programming with Java, Updated Edition (Addison-Wesley, 2000).
TextWindow is-a
Window
TextWindow subclasses Window,
all behavior of Window present in instances of
TextWindow
Window should be able to
hold value of type TextWindow
Unfortunately, practical programming language implementation issues complicate this idealized picture.
Support for inheritance and the principle of substitutability impacts most aspects of a programming language:
class Shape
{ public Shape (int ix, int iy) { x = ix; y = iy; }
public String describe() { return "unknown shape"; }
protected int x;
protected int y;
}
class Square extends Shape
{ public Square(int ix, int iy, int is) { super(ix, iy); side = is; }
public String describe() { return "square with side " + side;}
protected int side;
}
class Circle extends Shape
{ public Circle(int ix, int iy, int ir) { super(ix, iy); radius = is; }
public String describe() { return "circle with radius " + radius;}
protected int radius;
}
class ShapeTest
{ public static void main(String[] args)
{ Shape form = new Circle(10, 10, 5);
System.out.println("form is " + form.describe();
}
}
Output of ShapeTest: form is circle with
radius 5
Solitaire)
public class CardPile { ... }
class SuitPile extends CardPile { ... }
class DeckPile extends CardPile { ... }
class DiscardPile extends CardPile { ... }
class TablePile extends CardPile { ...}
public class Solitaire
{ ...
static public CardPile allPiles [ ];
...
public void init ()
{ // first allocate the arrays
allPiles = new CardPile[13];
...
allPiles[0] = deckPile = new DeckPile(335, 30);
allPiles[1] = discardPile = new DiscardPile(268, 30);
for (int i = 0; i < 4; i++)
allPiles[2+i] = suitPile[i] =
new SuitPile(15 + (Card.width+10) * i, 30);
for (int i = 0; i < 7; i++)
allPiles[6+i] = tableau[i] =
new TablePile(15 + (Card.width+5) * i, Card.height + 35, i+1);
}
private class SolitaireFrame extends Frame
{ ...
public void paint(Graphics g)
{ for (int i = 0; i < 13; i++)
allPiles[i].display(g);
}
}
Generally, programming languages use two different techniques for allocation of memory:
Variable of type Shape holds address of object on heap
Assignment of Square instance to Shape
variable means new address stored
Variable of type Shape holds actual object
Assignment of Square instance to Shape
variable means object copied with extra field sliced
off -- no longer Circle
Assignment of Square pointer to Shape
pointer variable means new address stored
Care must be taken with pointers to deallocated objects on stack (or in heap memory)
Examples: Java assignments to primitive variables, C++ assignments to non-pointer variables
Example: Java assignments to object variables, C++ assignments to pointer variables.
public class Box
{ public Box() { value = 0; }
public void setValue(int v) { value = v; }
public int getValue() { return value; }
private int value;
}
public class BoxTest
{ static public void main(String[] args)
{ Box x = new Box();
x.setValue(7); // sets value of x
Box y = x; // assign y the same value as y
y.setValue(11); // change value of y
System.out.println("contents of x " + x.getValue());
System.out.println("contents of y " + y.getValue());
}
}
y = x, both x and
y in BoxTest refer to the same object
y.setValue(11) thus changes object referred to
by both x and y
getValue() thus returns same value (11) for
both x and y
Box y = new Box(x.getValue());
public class Box
{ ...
public Box copy()
{ Box b = new Box();
b.setValue(getValue());
return b;
} // return (new Box()).setValue(getValue())
...
}
and use method when needed
Box y = x.copy();
public class Box
{ ...
public Box(Box x) { value = x.getValue()); }
...
}
Object has protected method
clone() that creates a bitwise copy of the receiver
Cloneable denotes objects that can be
cloned -- not methods in interface, just tag
Cloneable objects needed by some API methods
Make Box a Cloneable object
Cloneable
clone()
(which returns type Object)
clone() public
public class Box implements Cloneable
{ public Box() { value = 0; }
public void setValue(int v) { value = v; }
public int getValue() { return value; }
public Object clone()
{ return (new Box()).setValue(getValue()); }
private int value;
}
public class BoxTest
{ static public void main(String[] args)
{ Box x = new Box();
x.setValue(7); // sets value of x
Box y = (Box) x.clone(); // assign copy of x to y
y.setValue(11); // change value of y
System.out.println("contents of x " + x.getValue());
System.out.println("contents of y " + y.getValue());
}
}
BoxTest
Suppose that the values being held by the Box objects
are themselves objects of type Shape
Box's clone would not
copy the Shape object; the clones would both refer to the
same Shape object (instead of ints)
It creates a shallow copy
Shape object were also copied, then
it would be a deep copy.
Box's method clone() could call
Shape's clone() operation
Modification of parameter just local, no effect on argument
Note: Value of reference is copied from argument to parameter
Modification of parameter's internal state is change to argument
public class BoxTest
{ static public void main(String[] args)
{ Box x = new Box();
x.setValue(7); // sets value of x
sneaky(x);
System.out.println("contents of x " + x.getValue());
}
static void sneaky(Box y)
{ y.setValue(11); }
}
Value 11 would be printed by BoxTest
x == y
== tests object identity
Integer x = new Integer(7);
Integer y = new Integer(3 + 4);
if (x == y)
System.out.println("equivalent")
else
System.out.println("not equivalent")
not equivalent"
x and y physically distinct -- but
same value internally
==
Circle x = new Circle(10, 10, 5); Shape y = new Square(10, 10, 5); Shape z = new Circle(10, 10, 5);
x == y and x == z pass type
checking, but neither returns true
null is of type Object; can be compared
for equality with any object
Object class has equals(Object)
methods to do bitwise comparisons, often redefined
if (x.equals(y))
System.out.println("equivalent")
else
System.out.println("not equivalent")
equivalent"
equals() to get more appropriate
definition
class Circle extends Shape
{ ...
public boolean equals(Object arg)
{ return arg instanceof Circle &&
radius == ((Circle)arg).radius);
} // more compact above than textbook example
}
c.equals(d) iff c and
d are both Circles with same radius
regardless of location
equals() if object contains other
objects
equals() in Shape
class Shape
{ ...
public boolean equals(Object arg)
{ if (arg instanceof Shape)
{ Shape s = (Shape)arg;
return x == s.x && y == s.y ;
}
else
return false;
}
}
Square
Square s = new Square(10,10,5);
Circle c = new Circle(10,10,5);
if (s.equals(c)) // true, uses Shape method
System.out.println("square equal to circle");
if (c.equals(s)) // false, uses Circle method
System.out.println("circle equal to square");
For equality testing, it might useful to change types of method arguments
class Shape
{ ...
public boolean equals (Shape s) { return false; }
}
class Circle extends Shape
{ ...
public boolean equals (Circle c) { ... }
}
class Square extends Shape
{ ...
public boolean equals (Square sq) { ... }
}
Type replaced by descendant of original type
Java and C++ forbid
Eiffel supports covariance
Polymorphic variables lead naturally to heap-based allocation
Heap-based allocation requires a storage deallocation mechanism
Two approaches:
Examples: C++ delete, Pascal dispose
Advantage: efficiency
Disadvantages:
Examples: Java, Smalltalk, Perl
Advantage: safety and convenience
Disadvantage: relative inefficiency / loss of programmer control
UP to ENGR 691 Lecture Notes root document?