file: 04.1.codeReuseAndJava.txt author: Bob Muller date: February 1, 2014 CS102 Computer Science 102 Spring 2014 Lecture Notes for Meeting 1 of Week 4 Topics: 0. Perspective 1. Monomorphism and Polymorphism 2. Subtype Polymorphism and Object-Oriented Programming 3. OO Implementation Inheritance should be avoided use Composition instead ----------------------------------------------------------------- Notes: 1. How the java -this- variable works: Consider: public class A { private int x; public A(int x) { this.x = x; } public void add(int y) { this.x = this.x + y; } public static void main(String[] args) { A a = new A(4); a.add(5); } } The variable -this- is provided as an input, i.e., an argument, to each non-static function, as though the above code were actually written as follows: public class A { private int x; public A(int x) { this.x = x; } public void add(A this, int y) { // ************ this.x = this.x + y; } public static void main(String[] args) { A a = new A(4); a.add(a, 5); // ************ } } ----------------------------------------------------------------- 0. Perspective Looking back over the first three weeks, we've seen 7 main ideas: 0. An introduction to Java 1. ADTs: specification + implementation 2. Types, and Type-Directed Programming 3. APIs 4. The Stack and Queue ADTs 5. Parametric Polymorphism/Generics 6. Sequential .vs. Linked Representation Looking ahead, there are a few major peaks: 1. accounting for the amount of -work- carried out by an algorithm, 2. a survey of data structures that are sensitive to an -order-ing of the elements contained in the structure, 3. representations of finite maps, 4. mutability .vs. immutability. -------------------------------------------------------------------- 1. Monomorphism and Polymorphism Mono (one) morphism (shape or meaning) int square(int x) { return x * 2; } The square function maps ints to ints. It isn't applicable to anything but an int so we say that it is monomorphic. Parametric Polymorphism We've seen that a declaration: Queue q = new LinkedQueue(); gives rise to the function: public void enqueue(String info); while, re-using the same code in another declaration: Queue q = new LinkedQueue(); gives rise to the function: public void enqueue(Boolean info); So we say that the generic version of the function: public void enqueue(T info); is -reusable- or poly (many) morphic (shaped, meanings). This function can be used and reused on ANY reference type that is provided as a type argument, e.g., String or Boolean, etc. ===== BEGIN DIGRESSION ===== Dynamic Typing It's worth noting that the Python function: def makeTriple(x): return (x, x, x) is also polymorphic --- it will make a 3-tuple of any type of values. This form of polymorphism has a different character than the Queue example above because Python does not use type annotations. Compare: int square(int x) ... with def makeTriple(x) ... In Python, types are checked when the program executes, i.e., at run-time, rather than when the program is compiled i.e., at compile-time. Run-time checking is usually called -dynamic typing-. It goes without saying that in order to check types at run-time, there must be representations of types to check at run-time. In Java, most types are checked when the program is compiled. This is called -static typing-. Static typing has two major advantages: 1. early detection of errors (many errors that would be red ink in Python are yellow ink in Java), 2. as mediators of composition, types are invaluable in structuring our thought processes about computation. ===== END DIGRESSION ===== ----------------------------------------------------------------- 2. Subtype Polymorphism and Object-Oriented Programming " ... compounds marketed as panaceas or miraculous remedies whose ingredients were usually secret, unidentified, or mischaracterized and mostly inert or ineffective." The sort of polymorphism exhibited in the Queue examples is called -parametric polymorphism-. This term captures the fact that the uses of the functions at multiple types arise from providing types (e.g., String, Boolean) as -parameters-. Thus, one says: Queue myQueue = ... and we get the Boolean version, etc. We can pass in ANY reference type. There are other sorts of polymorphism including, e.g., intersection polymorphism and subtype polymorphism. Java was originally conceived as a so-called "object-oriented" programming language. This was very trendy in the 1980s and 90s. Unfortunately, it is still in wide use today. The kind of polymorphism present in OO languages is (essentially) subtype polymorphism. The main animating idea behind OO-programming is to support the reuse of code by the organization of implementations in a tree-structured hierarchy. This gives rise to the two touch-stones of OO: 1. Inheritance, 2. Dynamic Dispatch. Remember our definition of Cartesian Points. public interface Point { ... } public class PointC implements Point { ... } Now imagine that a need arises for both regular points and points that have a weight, some are heavier than others. Intuitively, a WeightedPoint is just a special case of a Point: Object | | +--------------------+ | Point | Point | | | +----------------+ | | | | WeightedPoint | | | | | | | | +----------------+ | WeightedPoint +--------------------+ So in implementing an ADT of WeightedPoints, we surely ought to be able to -reuse- much of the code from Point.(!) The main animating idea of object-oriented programming, and of Java, WAS (past-tense) to write something like: public interface WeightedPoint extends Point { public double getWeight(); } public class WeightedPointC extends PointC implements WeightedPoint { ... } SEE THE POSTED CODE. OO Terminology/Jargon + The class WeightedPointC is said to be a -subclass- of class PointC, + The class PointC is said to be a -superclass- of class WeightedPointC, + The class WeightedPointC is said to -inherit- public (or protected) definitions from PointC, + the value of a "new ClassName(...)" expression is sometimes called an: + -object- + -instance- of ClassName, + functions defined in classes are usually called -methods-. Dynamic Dispatch Referring to the linked code, note that that resolution of which toString code to execute for: Point p = new WeightedPointC(.1, .2, .3); System.out.println(p.toString()); is called -dynamic dispatch- or -late binding-. Languages that implement late binding are sometimes said to be "dynamic languages". Java's built-in classes are organized in this sort of tree-structured hierarchy. The root of the hierarchy is the class Object. All user defined classes inherit from the Object class. Important functions/methods that all classes inherit from Object include: boolean equals(Object other); String toString(); int hashCode(); Java programmers (and OO programmers in general) were encouraged to achieve code re-use by organizing their own classes in user-defined hierarchies using class extension. -------------------------------------------------------------------- Object-oriented programming has been a massive and pervasive "movement" that consumed the world of software for the last 30 years or so. That era appears to be coming to an end, in part because better approaches are available and in part because most of the fundamental ideas behind object-oriented programming have been discredited. In particular, class extension, the raison d'etre of object-oriented programming, has many significant problems. To cite two: 1. From a methodological point of view, it is simply not the case that all of the types that one might like to repurpose a function to operate on are naturally arranged in a tree-structured hierarchy. My Stack ADT should be able to house ANY reference types Points, Vehicles, Friends, ... It is true that GUI components actually can be organized fairly naturally in a tree-structured manner, but most other applications don't lend themselves to this kind of structuring. 2. Class extension has significant technical problems that break encapsulation, one of the bedrock properties of ADTs. + See SW p. 101, subsection: "Implementation Inheritance" which includes the sentence: "The use of subclassing is controversial among systems and applications programmers (its advantages over interface inheritance are debatable), and we avoid it in this book because it generally works against encapsulation." + For a fuller discussion of how implementation inheritance breaks encapsulation see Bloch: Item 16: Favor composition over inheritance ===== Begin Problematic Example from Item 16 of Bloch ====== // Broken - Inappropriate use of inheritance! // public class InstrumentedHashSet extends HashSet { // The number of attempted element insertions // private int addCount = 0; public InstrumentedHashSet() {} public InstrumentedHashSet(int initCap, float loadFactor) { super(initCap, loadFactor); } @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(Collection c) { addCount += c.size(); return super.addAll(c); } public int getAddCount() { return addCount; } } This class looks reasonable, but it doesn’t work. Suppose we create an instance and add three elements using the addAll method: InstrumentedHashSet s = new InstrumentedHashSet(); s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); We would expect the getAddCount method to return three at this point, but it returns six. ===== End Problematic Example from Item 16 of Bloch ====== So as a sort of "best practice" --- modern Java programmers are advised to avoid class extension.(!) As far as pedagogy is concerned, another significant problem with OO in general and Java in particular is the ill-defined and confusing jargon that leaves even java experts confused as to what they're talking about and leaves them unable to reason clearly about computation. There are many other problems with the OO paradigm. In CS102 we'll cherry-pick the reasonable parts of Java, primarily the presence of types and interfaces, and tip-toe around most of the OO parts. -------------------------------------------------------------------- 3. OO Implementation-inheritance should be avoided use composition instead It turns out that one can achieve the goals of code reuse in Java using approaches that are actually sound and scale up. The main tool is the generic type variable. But in the case of WeightedPoint's we would instead use -composition- of types. In particular, we would write: public class WeightedPointC implements WeightedPoint { private Point myPoint; private double weight; ... SEE THE POSTED CODE ... } + Like the implementation inheritance approach, this approach also -reuses- the code for the implementations of the operations on Points. + Unlike the inheritance approach, this implementation of WeightedPoint respects the abstraction barrier of the Point ADT --- it makes requests of the Point ADT using only the operations advertised in the Point API. ---------------------------------------------------------------------- Summary The fundamental things to be clear about are: 1. Programming languages have various forms of -expressions-, 2. Expressions can be -evaluated- and may yield -values-, 3. Expressions are associated with -types- that characterize the sorts of values that the expressions might yield.