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(..)
…