This is a set of "slides" to accompany chapter 11 of Timothy Budd's textbook Understanding Object-Oriented Programming with Java (Addison-Wesley, 1998).
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 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 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(Sring[] 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(Sring[] 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 int
s)
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(Sring[] 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 Circle
s 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 were discussed previously
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 CSCI 581 Lecture Notes root document?