Implications of Inheritance

Download Report

Transcript Implications of Inheritance

Engr 691
Special Topics in Engineering Science
Software Architecture
Spring Semester 2004
Lecture Notes
Implications of Inheritance
(Budd's UOOPJ, Ch. 11)
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)
Idealization of is-a Relationship
A TextWindow is-a Window
Because TextWindow subclasses Window, all behavior of
Window present in instances of TextWindow
Therefore, variable declared as Window should be able to
hold value of type TextWindow
Unfortunately, practical programming language
implementation issues complicate this idealized picture.
Impact of Inheritance on
Other Language Features
Support for inheritance and principle of substitutability impacts most
aspects of a programming language:
Polymorphic variables needed
Polymorphic variable – variable declared as one type but actually
holds value of subtype
Polymorphic variables better if objects allocated on heap rather than
stack – storage requirements vary among subtypes
Heap allocation
– makes reference (pointer) semantics more natural than copy semantics
for assignment and parameter passing – copy address of object rather
than value
– makes reference semantics more natural for object identity testing –
compare addresses – separate operator for object value equality
– requires storage management – reference semantics makes manual
allocation difficult – garbage collection encouraged
Polymorphic Variables
(class Shape)
class Shape
{
public Shape (int ix, int iy)
{
x = ix;
y = iy;
}
public String describe()
{
return "unknown shape";
}
protected int x;
protected int y;
}
Polymorphic Variables
(class Square)
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;
Polymorphic Variables
(class Circle)
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;
}
Polymorphic Variables
(class ShapeTest)
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
Polymorphic Variables
(class CardPile from Solitaire)
public class CardPile { ... }
class SuitPile extends CardPile { ... }
class DeckPile extends CardPile { ... }
class DiscardPile extends CardPile { ... }
class TablePile extends CardPile { ...}
Polymorphic Variables
(class Solitaire from Solitaire Example)
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 );
}
}
Polymorphic Variables
(class SolitaireFrame from Solitaire)
private class SolitaireFrame extends Frame
{
...
public void paint(Graphics g)
{
for (int i = 0; i < 13; i++)
allPiles[i].display(g);
}
}
Memory Allocation
in Programming Languages
Static allocation
Stack-based allocation
Heap-based allocation
Memory Allocation
Stack-based Allocation
Memory allocated dynamically on runtime stack
 Memory allocation/release tied to procedure entry/exit
 Space requirement determined at compile time based
on static types
 Advantage: efficient – all local variables
allocated/deallocated as a block (activation record)
 Disadvantage: polymorphic variable size not known at
compile time – objects stored may vary during
execution
Memory Allocation
Heap-based Allocation
Memory allocated dynamically from free memory
area
 Memory allocation/release not tied to procedure
entry/exit
 Space requirement determined at run-time based
using dynamic considerations – size known when
allocated
 Allocated objects accessed by indirection through a
pointer (reference in Java)
 Advantage: supports polymorphic variables – values
can be pointers to object on heap
 Disadvantage: considered less efficient than stackbased
Memory Allocation in Java
All object variables hold pointers to heap-allocated
objects – fixed size on stack, differing sizes on heap
– variable of type Shape holds address of object on heap
Polymorphic variables thus easy to implement
– assignment of Square instance to Shape variable means new
address stored
Primitive type variables hold values, copy on
assignment, not polymorphic
Memory Allocation in C++
Variables stored on stack – enough space allocated to hold instance
of actual declared type
– variable of type Shape holds actual object
"Ordinary" variables are not polymorphic
– assignment of Square instance to Shape variable means object copied
with extra field sliced off – no longer Circle
Pointer variables hold addresses of objects – thus support
polymorphism
– assignment of Square pointer to Shape pointer variable means new
address stored
Objects may be allocated on heap (or on stack or statically)
– care must be taken with pointers to deallocated objects on stack (or in
heap memory)
Copy versus Reference
Semantics
Copy semantics:
 assignment copies entire value of right side to left-side variable
 the two values are independent; changes to one do not affect
other
 examples: Java assignments to primitive variables,
C++ assignments to non-pointer variables
Reference (pointer) semantics:
 assignment changes left-side variable to refer to right-side value
 now two references to same value; if value is changed, it can be
observed using either reference
 examples: Java assignments to object variables
C++ assignments to pointer variables.
Java Reference Semantics
Example
public class Box
{
public Box()
{ value = 0; }
public void setValue(int v)
{ value = v; }
public int getValue()
{ return value; }
}
private int value;
Java Reference Semantics
Example (continued)
public class BoxTest
{
static public void main(String[] args)
{
Box x = new Box();
x.setValue(7);
// sets value of x
Box y = x;
y.setValue(11);
}
}
// assign y the same value as y
// change value of y
System.out.println("contents of x " + x.getValue());
System.out.println("contents of y " + y.getValue());
After y = x, both x and y in BoxTest refer to the same object
Call y.setValue(11) thus changes object referred to by both x and y
Message getValue() thus returns same value (11) for both x and y
Creating Copies in Java
If need copy, explicitly create it
Box y = new Box(x.getValue());
If commonly need copy, provide copy-creating method
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();
Creating Copies in Java
Copy constructors are sometimes useful
public class Box
{ ...
public Box(Box x)
{ value = x.getValue()); }
...
}
Base class Object has protected method clone() that
creates bitwise copy of receiver
Interface Cloneable denotes objects that can be
cloned – no methods in interface, just tag
– objects needed by some API methods
Example: Java Clones
Make Box a Cloneable object
Implement interface Cloneable
Override method clone() (which returns type Object)
Make clone() public
Example: Java Clones (cont.)
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;
Example: Java Clones (cont.)
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());
Values 7 and 11, respectively, would be printed by BoxTest
Shallow versus Deep Copying
Suppose values being held by Box objects are
themselves objects of type Shape (instead of int)
Box's clone() would not copy Shape object
– clones would both refer to same Shape object
– clone() creates a shallow copy
If internal Shape object also copied, then it is a
deep copy.
– Box's method clone() could call Shape's clone() operation
Decide whether shallow or deep copy is needed for
application
Parameter Passing as
Assignment
Parameter-passing is assignment from argument to parameter
Java primitive values are passed by value from argument to
parameter – copy semantics
– modification of parameter just local, no effect on argument
Java object variables are passed by reference from argument to
parameter – reference semantics
Note: Value of reference is copied from argument to parameter
Modification of parameter's internal state is change to argument
Parameter Passing as
Assignment (continued)
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
Equality Testing
Primitive Types
How test whether two values of same primitive type are equal?
 Test whether their values are identical – i.e., same bits
 Java: x == y
What about equality of values of different primitive types?
 In general, will not pass type checker unless well-accepted conversion
between
 Java: numeric types converted and compared, but otherwise
mismatched types means inequality
Equality Testing
Object Identity
Some languages will compare the values; others, compare pointers
(references)
Java uses pointer semantics – i.e., tests object identity
Java == 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")
Output is "not equivalent"
Equality Testing
Object Identity (continued)
Objects x and y physically distinct – but same value internally
Java type checker disallows comparison of unrelated object types with
==
But can compare if one an ancestor of other
Circle x = new Circle(10, 10, 5);
Shape y = new Square(10, 10, 5);
Shape z = new Circle(10, 10, 5);
Above 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
Equality Testing
Object Value Equality
Java Object class has equals(Object) method to do
bitwise comparisons, often redefined
Continuing the Shape/Circle from the previous page
if (x.equals(y))
System.out.println("equivalent")
else
System.out.println("not equivalent")
Output is "equivalent"
Equality Testing
Object Value Equality (cont.)
Can override 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
}
Above c.equals(d) iff c and d are both Circles with same radius
regardless of location
Should override equals() if object contains other objects
Equality Testing
Object Value Equality (cont.)
But be careful with asymmetric equality comparisons
Suppose override 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;
}
}
But not in subclass Square
Equality Testing
Object Value Equality (cont.)
Now consider
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");
Changing Method Arguments
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) { ... }
}
Covariance and Contravariance
An argument or return value made more specialized is
covariant
 type replaced by descendant of original type
An argument made more general is contravariant
Both can destroy is-a relation, have tricky semantics
Most languages forbid both
 Java and C++ forbid
 Eiffel supports covariance
Storage Deallocation
Polymorphic variables lead naturally to heapbased allocation
Heap-based allocation requires a storage
deallocation mechanism
Two approaches:
 Explicit deallocation by programmer
 Implicit deallocation by runtime system
Storage Deallocation (continued)
Explicit deallocation by programmer
Programmer must return unneeded memory to
system Examples: C++ delete, Pascal dispose
Advantage: efficiency
Disadvantages:
 attempted use of memory not yet allocated or already freed
 multiple freeing of memory
 freeing of memory that is still needed
 memory leak – allocated memory is never released
Storage Deallocation (continued)
Implicit deallocation by runtime system
System detects when data unneeded, automatically
recovers memory
Garbage collection
Examples: Java, Smalltalk, Perl
Advantage: safety and convenience
Disadvantage: relative inefficiency / loss of
programmer control