Transcript Lecture 2

Implications of Inheritance
COMP206, Geoff Holmes and
Bernhard Pfahringer
Implications





OO language must support polymorphic
variables
Polymorphic variables: memory requirements
unknown at compile-time => allocated on the
heap
Heap allocation => reference semantics for
assignment and parameter parsing
Also: equality testing == object identity
And: memory management necessary =>
garbage collection (GC)
The Polymorphic variable
Shape
x,y
describe()
Square
side
describe()
Circle
radius
describe()
…
Shape form = new Circle(10,10,5);
form.describe();
form = new Square(15,20,10);
form.describe();
…
Memory Layout


Stack-based vs. heap-based
Stack-based tied to method entry/exit:






Allocate space for all local vars on entry
Deallocate on exit
Access by numeric offset (activation record)
Need to know space requirements at compile-time, not
possible for polymorphic variables!
Java: all objects allocated on the heap (new
operator), local vars point to these objects, pointers
have a fixed size
Java has no pointers, but internally all object values
are represented by pointers.
Factorial example
static public int factorial(int n) {
int c = n-1;
int r;
if (c>0)
r = n*factorial(c);
else
r = 1;
return r;
}
Factorial(3)
0
4
8
0
4
8
0
4
8
n:1
r:1
c:0
n:2
r:?
c:1
n:3
r:?
c:2
third
activation
record
second
activation
record
first
activation
record
Alternative approach (e.g. C++)



Different approaches are possible
C++: want to use stack (efficiency)
Assignment: extra fields are sliced off,






E.g. a Square is turned into a Shape,
Need to distinguish between virtual/non-virtual
methods
Need destructors,
Pointers/references/values
Copy-semantics,
…
Assignment: reference semantics
Box x = new Box();
x.setValue(7);
Box y = x;
y.setValue(11);
System.out.println(x.getValue());
System.out.println(y.getValue());
…
11
x
11
a box
y
Simple copy pointer value, i.e. point to same heap-location!
Clones

If copy desired, must say so, DIY approach:
Box y = new Box();
y.setValue(x.getValue());

If common, package into proper method:
public Box copy() {
Box b = new Box();
b.setValue(getValue());
return b;
}
Clones: Java


Class Object provides protected method clone() creating a
bitwise copy of the receiver, plus interface Cloneable
Programmer must override clone() to public and implement
Cloneable:
public class Box implements Cloneable {
…
public Object clone() {
Box b = new Box();
b.setValue(getValue());
return b;
}}

Use: Box y = (Box) x.clone(); // must cast !!!!
Clones: caveats

Just a shallow copy, sometimes need
deep copies
x
a box
y
a box
x
a box
a shape
y
a box
a shape
a shape
SHALLOW
DEEP
Parameter passing as assignment

Passing a variable considered similar to
assignment, as same value accessible
through two different names; pass value,
loose some control:
static void sneaky (Box b) {b.setValue(11);}
…
x.setValue(7); sneaky(x);
System.out.println(x.getValue());
11
Equality test: object identity

Easy for primitives:




For objects: == implements object identity,
therefore:


7 == (3+4)
‘a’ == ‘\141’
2 == 2.0
new Integer(7) != new Integer(3+4)
If we really want object equality instead of
object identidy: equals method
Equality test: object equality

Supplied by class Object, can be overridden:
class Circle extends Shape { …
public boolean equals(Object arg) {
return ((arg instanceof Circle) &&
(radius == ((Circle) arg).radius));
}}


Careful: must be symmetric and transitive
Heuristic: use == for numbers and null,
equals in all other situations
Equality test: bug example

Suppose:
class Shape { …
public boolean equals(Object arg) {
return ((arg instanceof Shape) &&
(x == ((Shape) arg).x) &&
(y == ((Shape) arg).y)) ;
}}

And no equals defined in class Square, then:
Square s = new Square(10,10,5);
Circle c = new Circle(10,10,5);
s.equals(c); // succeeds
c.equals(s); // fails
Better solution
class Shape { …
public boolean equals(Object arg) {
return ((arg.getClass() == Shape.class) &&
(x == ((Shape) arg).x) &&
(y == ((Shape) arg).y)) ;
}}
class Circle extends Shape { …
public boolean equals(Object arg) {
return ((arg.getClass() == Circle.class) &&
(x == ((Shape) arg).x) &&
(y == ((Shape) arg).y) &&
(radius == ((Circle) arg).radius));
}}

correct, but no inheritance, code duplication
Garbage collection (GC)


Heap-based memory not automatically
recovered on method exit (unlike stack)
Manual management error-prone (like “free”
operator in C++):




Forget to free: memory leaks
Free multiple times
Access freed memory
Java compromise: have automatic garbage
collection:

some runtime cost, but not too bad)
Immutable objects




cannot be changed after “construction”
how to:
 do not allow modification by not providing “mutators”
(methods that change state)
 making data fields “private” (always good idea)
 (most likely) prevent against subclassing
useful for “value” types, like Integer, String, and so on; safe to
use as keys for HashMaps (see code example for explanation)
hashCode “rule”:

a.equals(b) == true => a.hashCode() == b.hashCode()
[only this direction, different objects can have the same
hashCode]
Object construction

no explicit constructor: system adds “default
constructor”:



public A() { super(); }
At least one constructor given explicitly: no “default
constructor” will be added
first line in the constructor can explicitly pass on to
constructor of the super-class or same class (if not,
system will add “super();”)


public A() { super(“info”); /* more code */ }
public A() { this(0); /* more code */ }
Construction order


Data fields are allocated and initialized to default
values (0, null, …) *before any* code blocks or
constructors;
Then the process is top-down (most general class
first to most specific last), each time:



Data fields are initialized to their actual values (e.g. 1 in the
code example), in order
Local code blocks are run, in order
The respective constructor is run
Bad Inheritance example
import java.util.*;
public class InstrumentedArrayList extends ArrayList {
// The number of attempted element additions
private int addCount = 0;
public InstrumentedArrayList() {
}
public InstrumentedArrayList(Collection c) {
super(c);
}
public InstrumentedArrayList(int initialCapacity) {
super(initialCapacity);
}
cont.
public void add(int index, Object o) {
addCount++;
super.add(index,o);
}
public boolean addAll(Collection c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() { return addCount; }
public static void main(String[] args) {
InstrumentedArrayList s = new InstrumentedArrayList();
s.addAll(Arrays.asList(new String[] {”A",”B",”C"}));
System.out.println(s.getAddCount());
}
}
Better: use Composition
import java.util.*;
public class InstrumentedList implements List {
private final List s;
private int addCount = 0;
public InstrumentedList(List s) {
this.s = s;
}
public void add(int index, Object o) {
addCount++;
s.add(index, o);
}
public boolean addAll(Collection c) {
addCount += c.size();
return s.addAll(c);
}
Composition, cont.
public int getAddCount() {
return addCount;
}
// plus all necessary forwarding methods
public void clear() { s.clear(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
… and so on …
Utilities: Arrays class, Collections

lots of useful utilities, have a look at the Javadoc, e.g.

Arrays.sort(…)

Arrays.binarySearch(..)

Arrays.toString(..)

Arrays.fill(..)

Arrays.hashCode(..)

Arrays.equals(..)

Arrays.asList(..)

Arrays.deepEquals(..), deepToString(..) deepHashCode(..)

Collections.shuffle(..)
Utilities: System class

again lots of useful utilities, have a look at the Javadoc, e.g.
 System.arraycopy(..)
 System.nanoTime()
 System.identityHashCode(..)
 System.getenv(..)
 …