1 Subclassing Object Oriented Programming 236703 Spring 2007 2 A Tale of Two Objects // Object 1: "abc" "abc".length == 3 "abc".charAt(1) == 'b' "abc".charAt(2) == 'c' // Object 2: "def" "def".length == 3 "def".charAt(1) == 'e' "def".charAt(2) == 'f' Q: How are Object 1 and Object 2 different? A (Hypothetical) Tale of Two Objects // Object 3: "uvw" "uvw".length == 3 "uvw".charAt(1) == 'v' "uvw".charAt(2) == 'w' // Object 4: "xyz" "xyz".length == 3 "xyz".charAt(1) == 'x' // ?! "xyz".charAt(2) == 'y' Q: How are Object 3 and Object 4 different? 3 4 Differences Between Objects ● ● Recall: A class determines the behavior & structure of its instances (For a given class C), if we want: – A new object with a different state: ● ● – Just create a new instance of C, and set its fields as needed (A class is an abstraction over sate) A new object with a different behavior/structure ● Introduce D – ● A new class that provides the requested behavior/structure Create an instance of D Behavioral Differences "uvw".length() == 3 "uvw".charAt(1) == 'v' "xyz".length() == 3 "xyz".charAt(1) == 'x' ● ● xyz's should provide a different behavior for the charAt() message The solution: let's define two String classes 5 Deja-vu class StrZero { char[] cs; public StrZero(char... arr) { cs = arr; } public int length() { return cs.length; } public char charAt(int i) { return cs[ i]; } } class StrOne { char[] cs; public StrOne(char... arr) { cs = arr; } public int length() { return cs.length; } public char charAt(int i) { return cs[ i-1]; } } ● Code duplication: Body of StrOne is almost identical to that of StrZero 6 Subclassing: Avoid the repetition class StrZero { private char[] cs; public StrZero(char... arr) { cs = arr; } public int length() { return cs.length; } public char charAt(int i) { return cs[i]; } } class StrOne extends StrZero { public StrOne(char... arr) { super(arr); } public char charAt(int i) { return cs[i-1]; } } ● ● Subclassing: New class specifies the delta from an existing class – Structure + Protocol + Behavior of superclass is copied onto the body of the subclass – In most languages Type is also copied (see next slide) – Forge is copied but is accessible only from constructors – AKA: Copying principle Result: less code duplication (thanks to the copying principle) 7 8 Subclassing vs. Subtyping ● ● ● ● ● “A subclasses B”: Structure+Protocol+Behavior of B is copied into A “A subtypes B”: An object of type A is assignable into a variable of type B In Java: “A extends B” = “A subclasses B” + “A subtypes B” This equation is true in most languages Sometimes we only want a subclass, not a subtype – (We'll see some examples later...) StrOne Subtypes StrZero ● (Remember: subtyping means assignability) ● An StrOne object is assignable to an StrZero variable: StrZero s = new StrOne('a', 'b'); ● ● Polymorphism: The ability to take more than one form – Main benefit: Reduces deja-vu The sizeIs method is polymorphic: boolean sizeIs(StrZero s, int n) { return s.size() == n; } – sizeIs() can work with either StrZero ● Or any other subtype of StrZero – – or StrOne objects This is an example of Subtyping-based polymorphism ● Aka: inclusion polymorphism Actually, the message/method distinction implies subtypingbased polymorphism 9 Subclassing a Point Class public class Point2d { private int x, y; public Point2d(int x0, int y0) { x = x0; y = y0; } public int getX() { return x; } public int getY() { return y; } public int moveRight(int dx) { x += dx; } public int moveUp(int dy) { y += dy; } public String toString() { return "[" + x + "," + y + "]"; } public boolean isOrigin() { return x == 0 && y == 0; } } public class Point3d extends Point2d { private int z; public Point3d(int x0, int y0, int z0) { super(x0, y0); z = z0; } public int getZ() { return z; } public int moveIn(int dz) { z += dz; } public String toString() { return "[" + x + "," + y + "," + z + "]"; } public boolean isOrigin() { return super.isOrigin() && z == 0; } } 10 Three Choices public class Point3d extends Point2d { private int z; public Point3d(int x0, int y0, int z0) { super(x0, y0); z = z0; } public int getZ() { return z; } public int moveIn(int dz) { z += dz; } public String toString() { return "[" + x + "," + y + "," + z + "]"; } public boolean isOrigin() { return super.isOrigin() && z == 0; } } ● Three options for every member in a subclass – Introduced by the subclass: int z, getZ(), moveIn() – Inherited from the superclass: getX(), getY(), ... – Redefined by the subclass: isOrigin(), toString() 11 Redefined Methods in Point3d public class Point3d extends Point2d { ... public String toString() { return "[" + x + "," + y + "," + z + "]"; } public boolean isOrigin() { return super.isOrigin() && z == 0; } } ... Point2d p = new Point3d(0,0,10); p.isOrigin(); // Result: False ● Redefined members are the most interesting ones – We have two competing definitions – The language should define the semantics (i.e.: who's winning?) – Java's semantics for methods is overriding 12 Overriding Point2d p = new Point3d(0,0,10); p.isOrigin(); // Result: False ● ● ● ● Message: isOrigin() Method: Point3d.isOrigin() There are two types associated with a receiver: 1)Static type: type of the variable (here: Point2d) The type that is known at compile time 2)Dynamic type: type of the pointed object (here: Point3d) This type is known only at run-time – If we have subtyping, then it is possible that: static type ≠ dynamic type Two approaches for looking up a method from a message: – Static binding: Use the static type ● Lookup is carried out at compile-time – Dynamic binding: Use the dynamic type ● Lookup is carried out at run-time ● Compiler does not know what method is invoked 13 Binding Quiz public class X { public String toString() { return "X"; } // final means non-overrideable public final int addThree(int n) { return n + 3; } public static int mul(int a, int b) { return a * b; } } public class Y extends X { public final String toString() { return "Y" + super.toString(); } } X x = new X(); ... X x = new Y(); x.toString(); ... x.addThree(5); x.toString(); x.mul(3,9); x.addThree(5); x.mul(3,9); ● ● Y y = new Y(); ... y.toString(); y.addThree(5); y.mul(3,9); What's the binding of each call? What's the binding of super.toString()? 14 Refinement public class Point3d extends Point2d { public String toString() { return "[" + x + "," + y + "," + z + "]"; } public boolean isOrigin() { return super.isOrigin() && z == 0; } } ● ● Point3d.isOrigin() uses Point2d.isOrigin() – Such a method is said to be a refinement of its overridden method – Further eliminates code duplication ● Compare with: return x == 0 && y == 0 && z == 0 Can toString() become a refinement? – Yes 15 Variations on Refinement ● ● ● precursor(...)instead of super.methodName(...) – Eiffel has that Alpha refinement: overriding method calls overridden method – Java, C#, Eiffel, LST, ... Beta refinement: overridden method calls overriding method – Pros: ● Easier to ensure invariants – Cons: ● Designer of superclass must think of subclasses in advance ● What's the semantics if no subclass exists? // Pseudo code: Beta-refinement in Java public class X { public int n; public void print() { System.out.println("n=" + n); inner; // New keyword, invokes overriding method } } 16 Variations on Refinement (cont.) ● ● Daemons – A method definition has three parts: ● A “before” daemon; a body; An “after” daemon – Before daemons are invoked on a top-first order – After daemons are invoked on a bottom-first order Method combinators: Combining return values – From all overridden methods (in Alpha refinement) – From all overriding methods (in Beta refinement) – Typical combinators: add, append-to-list, append-to-set, ... 17 Subclass Affects Superclass public class Adder public int a, b; { public int add() { return a + b; } public void printResult() { System.out.println(add()); } } public class Subtracter extends Adder { public int add() { return a - b; } } ... Adder adder = new Subtracter(); adder.a = adder.b = 9; adder.printResult(); // Output: "0" ● ● ● This program is legal but confusing – adder.printResult()prints the difference ?! Also, sometimes a superclass cannot provide an implementation for a method Solution: abstract methods 18 Abstract Methods ● ● ● ● 19 An override-able method can be declared as abstract – Usually, this means that it has no body The resulting class is incomplete – It's protocol contains a certain message, with which no behavior is associated – We expect a subclass to complete the missing behavior – => In a statically typed language the compiler will not allow such classes to be instantiated Terminology: – Abstract class – Concrete class A subclass of an abstract class: – Is concrete if it defines overriding methods for all inherited abstract methods – Otherwise, it is abstract as well Subclass Affects Abstract Superclass public abstract class Operation protected int a, b; 20 { public abstract int compute(); public void printResult() { System.out.println(compute()); } } public class Adder extends Operation { public Adder(int x, int y) { a = x; b = y; } public int compute() { return a - b; } } public class Subtracter extends Operation { public Subtracter(int x, int y) { a = x; b = y; } public int compute() { return a - b; } } ... Operation o = new Adder(9, 9); o.printResult(); // Output: 18 o = new Subtracter(9, 9); o.printResult(); // Output: 0 Subclass Introduces a Bug public class Range { protected int b, e; public Range(int x, int y) { begin(x); end(y); } public void begin(int n) { b = n; } public void end(int n) { e = n; } public void move(int n) { begin(b+n); end(e+n); } } public class PositiveRange() { public PositiveRange(int begin, int end) { super(begin, end); check(); } private void check() { if(e <= b) throw new Exception("Invalid range"); } public void begin(int n) { super.begin(n); check(); } public void end(int n) { super.end(n); check(); } } 21 Strict Inheritance ● ● ● The overriding mechanism is very powerful – One can selectively replace parts of a superclass – As we've just seen it is almost too powerful... – Problem 1: overriding method expects too much – Problem 2: overriding method delivers too little A partial solution: Strict inheritance – A limited form of inheritance – Only two choices: introduce or inherit. ● No redefinition! – Eliminates the above-mentioned problems – Not as useful Most languages do not provide an explicit mechanism for strict inheritance – You can always use subclassing and just avoid redefinition 22