C311 script12.txt -- 4/15/97 --- OBJECT-ORIENTED PROGRAMMING: Part II --- INHERITANCE A SUBCLASS (a.k.a. a DERIVED CLASS or child class) is EXTENDS (the Java keyword) the behavior and/or state of a SUPERCLASS (a.k.a. an ANCESTOR or parent class) Method OVERRIDING (modifying inherited behavior) is generally possible by declaring a method with the same name as an inherited method. In some cases, e.g. Java, an overriding method must also be of the same type. SINGLE INHERITANCE: only one immediate ancestor; e.g. Java, Ada95, Smalltalk (usually) MULTIPLE INHERITANCE: multiple ancestors possible; e.g. C++, CLOS, Eiffel creates many problems If two inherited classes each define a method or variable of the same name, which one does the name denote in the subclass? In C++, such a reference must specify a superclass that disambiguates the reference. If a base class B is a subclass of classes C and D inherited by E, does an instance of E contain one or two copies of B's instance variables? B B B / \ | | If C D , is this the same as C D ? \ / \ / E E In C++, yes unless B is declared to be a VIRTUAL BASE CLASS. --- Java inheritance class declaration syntax: after class name, may insert "extends ClassName" this indicates implementation inheritance no extends clause is equivalent to "extends Object" the class Object is at the root of the class inheritance tree method modifier "final": can't be overwridden class modifier "final": cannot be a superclass method modifier "abstract": body not defined (a semicolon) Overriding of method identifier with a method definition is required before the method can be used. abstract class modifier contains one or more abstract methods cannot be instantiated The only point of abstract classes (or methods) is to increase the power of polymorphism. --- STATIC METHOD BINDING: methods are looked up in the method environment of the class that indicates the known class of the object. DYNAMIC METHOD BINDING: methods are looked up in the method environment of the class that created the instance that receives the method-name message. < picture on board > Can't tell the difference unless the method was overridden and is being invoked with a known class "below" the overriding. When there is a difference, you usually want dynamic method binding. Static method binding is the default in C++. Declare all methods to be virtual (dynamically bound) unless you are sure you want it to be static. If methods are not virutal, trouble is often not created until the class is reused. Static method bindings can be resolved at compile time in a statically typed language. Dynamic method bindings can be resolved with one level of indirection in a method table using an offset computed at compile time (in a statically typed language). In dynamically typed languages, caching of the last method access at each call cite dramatically reduces method lookup overhead (e.g. in Smalltalk and CLOS). --- A C++ example comparing static and virtual methods. #include class A { public: void f() { cout << "virtual " << (*this).v() << " static " << (*this).s() << endl; } protected: virtual int v() { return 1; } static int s() { return 1; } }; class B : public A { int v() { return 2; } int s() { return 2; } }; int main() { (*(new B())).f(); // prints "virtual 2 static 1" return 0; } --- The last example in Java: public class Virtual { public static void main(String[] args) { System.out.println((new B()).f()); // prints "2" } } class A { int f() { return this.v(); } int v() { return 1; } } class B extends A { int v() { return 2; } } --- THIS and SUPER In a number of O-O languages, the keyword THIS (in C++ and Java) or SELF (e.g. in Smalltalk) may be used in some contexts within method bodies as if it were a variable referring to the current instance (the instance used to invoke the current method). In a number of O-O languages (e.g. Java and Smalltalk) the keyword SUPER may be used in place of THIS to refer to the current instance, but with method lookup starting in the parent of the lexically enclosing method (and proceeding up the inheritance chain if necessary). Thus in Java the call this.m() might invoke a method in a subclass, while super.m() always invokes a method in a super class. Also, in Java the first statement of a CONSTRUCTOR (a method by the same name as its class, used to initialize instance variables), SUPER may be used as a method name (without specifying an instance) to invoke the constructor of the super class. --- Java example using THIS and SUPER: public class SuperEG { public static void main(String[] args) { C c = new C(); // prints "init Ainit B" System.out.println(c.f()); // prints 1 } } class A { A() { System.out.println("init A"); } int m() { return 1; } } class B extends A { B() { super(); System.out.println("init B"); } int m() { return 2; } int f() { return super.m(); } } class C extends B { int m() { return 3; } } --- Simulating higher-order functions in Java from "Pizza into Java: Translating theory into practice", by Martin Odersky and Philip Wadler, POPL'97. Extend Java with first-class functions, introduced by the keyword FUN and function types, indicated by -> type operator. Example: class Radix { int n = 0; // The radix method takes an integer and returns a function that // takes a character and returns a boolean. (char) -> boolean radix(int r) { // the fun syntax requires that the return type, here boolen, be given return fun (char c) -> boolean { n++; return '0' <= c && c < '0'+r; }; } String test() { (char) -> boolean f = radix(8); return f('0')+" "+f('9')+" "+n; } } The function body above refers to three sorts of variables: 1. a formal parameter, x 2. a free variable, r 3. an instance variable, n --- Translation into Java: // An abstract class is created for each function type. // Closure_CB is for the type (char) -> boolean abstract class Closure_CB { abstract boolean apply_CB(char c); } // A class is created for each FUN expression. class Closure_1 extends Closure_CB { // There is an instance variable for the receiver (the instance in // which the closure was created) and each free (non-instance) variable // of the FUN expression. Radix receiver; int r; // The class constructor takes the free variable values as arguments. Closure_1(Radix receiver, int r) { this.receiver = receiver; this.r = r; } // An application of the closure must be passed to the receiver // with the free variable value(s). boolean apply_CB(char c) { return receiver.apply_1(r, c); } } class Radix { int n = 0; // An apply method is generated for each closure created in the class. // The instance variables of the class are accessible to this method. boolean apply_1(int r, char c) { n++; return '0' <= c && c < '0'+r; } Closure_CB radix(int r) { return new Closure_1(this, r); } String test() { Closure_CB f = radix(8); return f.apply_CB('0')+" "+f.apply_CB('9')+" "+n; } } public class RadixTest { public static void main(String[] args) { Radix r = new Radix(); System.out.println(r.test()); // prints "true false 2" } } --- Example from assignment 11: import java.util.Vector; import java.util.Enumeration; class MapVector extends Vector { MapVector map((Object) -> Object f) { MapVector newMV = new MapVector(); for (Enumeration e = this.elements() ; e.hasMoreElements() ;) { newMV.addElement(f(e.nextElement())); } return newMV; } } public class TestMapVector { (Object) -> Object curryConcat(String s1) { return fun (Object s2) -> Object { return (Object)(s1 + (String)s2); } } public static void main(String[] args) { MapVector things = new MapVector(); things.addElement("one"); things.addElement("two"); things.addElement("three"); MapVector mv = things.map(curryConcat("plus ")); System.out.println(mv); } } --- END ---