Object-Oriented Design Lecture 11 CS 3500 Spring 2010 (Pucella) Tuesday, Feb 16, 2010

Similar documents
Subclassing for ADTs Implementation

20 Subclassing and Mutation

17 Multiple Inheritance and ADT Extensions

Object-Oriented Design Lecture 23 CS 3500 Fall 2009 (Pucella) Tuesday, Dec 8, 2009

16 Multiple Inheritance and Extending ADTs

Object-Oriented Design Lecture 3 CSU 370 Fall 2007 (Pucella) Friday, Sep 14, 2007

Object-Oriented Design Lecture 13 CSU 370 Fall 2008 (Pucella) Friday, Oct 31, 2008

11 Parameterized Classes

3 ADT Implementation in Java

13 Subtyping Multiple Types

Equality for Abstract Data Types

12 Implementing Subtyping

The Java Type System (continued)

CS 151. Linked Lists, Recursively Implemented. Wednesday, October 3, 12

Lecture 5: Implementing Lists, Version 1

21 Subtyping and Parameterized Types

15 Multiple Inheritance and Traits

8 Hiding Implementation Details

7 Code Reuse: Subtyping

Object-Oriented Design Lecture 21 CSU 370 Fall 2008 (Pucella) Tuesday, Dec 9, 2007

CSC Java Programming, Fall Java Data Types and Control Constructs

The list abstract data type defined a number of operations that all list-like objects ought to implement:

Algebraic Specifications

8 Understanding Subtyping

CS/ENGRD 2110 SPRING Lecture 7: Interfaces and Abstract Classes

17 From Delegation to Inheritance

CS152: Programming Languages. Lecture 11 STLC Extensions and Related Topics. Dan Grossman Spring 2011

Chapter 4 Defining Classes I

Lecture 3. COMP1006/1406 (the Java course) Summer M. Jason Hinek Carleton University

CMPSCI 187: Programming With Data Structures. Lecture 12: Implementing Stacks With Linked Lists 5 October 2011

CS558 Programming Languages

Code Reuse: Inheritance

INTERFACES IN JAVA. Prof. Chris Jermaine

CS/ENGRD 2110 FALL Lecture 7: Interfaces and Abstract Classes

CS558 Programming Languages

Cpt S 122 Data Structures. Course Review Midterm Exam # 2

CSIS 10B Lab 2 Bags and Stacks

CS/ENGRD 2110 SPRING 2018

ITI Introduction to Computing II

Review. CS152: Programming Languages. Lecture 11 STLC Extensions and Related Topics. Let bindings (CBV) Adding Stuff. Booleans and Conditionals

Lesson 10A OOP Fundamentals. By John B. Owen All rights reserved 2011, revised 2014

Day 4. COMP1006/1406 Summer M. Jason Hinek Carleton University

ITI Introduction to Computing II

Administration. Objects and Arrays. Objects. Agenda. What is an Object? What is a Class?

CS450 - Structure of Higher Level Languages

CS558 Programming Languages

Graphical Interface and Application (I3305) Semester: 1 Academic Year: 2017/2018 Dr Antoun Yaacoub

09/02/2013 TYPE CHECKING AND CASTING. Lecture 5 CS2110 Spring 2013

4 Object-Oriented ADT Implementations

6.005 Elements of Software Construction Fall 2008

Type Inference: Constraint Generation

Concepts of Programming Languages

Contents. I. Classes, Superclasses, and Subclasses. Topic 04 - Inheritance

Summer 2017 Discussion 10: July 25, Introduction. 2 Primitives and Define

The ArrayList class CSC 123 Fall 2018 Howard Rosenthal

COMP 161 Lecture Notes 16 Analyzing Search and Sort

Typed Racket: Racket with Static Types

Midterm Exam (REGULAR SECTION)

CS 61B, Spring 1999 MT3 Professor M. Clancy

CS 61B Discussion 5: Inheritance II Fall 2014

} Evaluate the following expressions: 1. int x = 5 / 2 + 2; 2. int x = / 2; 3. int x = 5 / ; 4. double x = 5 / 2.

COMP Lecture Notes Iterative and Recursive Procedures for Strings

Collections, Maps and Generics

List Functions, and Higher-Order Functions

Harvard School of Engineering and Applied Sciences Computer Science 152

ITI Introduction to Computing II

Lecture #23: Conversion and Type Inference

A Model of Mutation in Java

Conversion vs. Subtyping. Lecture #23: Conversion and Type Inference. Integer Conversions. Conversions: Implicit vs. Explicit. Object x = "Hello";

Object-Oriented Design Lecture 14 CSU 370 Fall 2007 (Pucella) Friday, Nov 2, 2007

Weiss Chapter 1 terminology (parenthesized numbers are page numbers)

These are notes for the third lecture; if statements and loops.

6.001 Notes: Section 6.1

Chapter 10 Recursion

3.Constructors and Destructors. Develop cpp program to implement constructor and destructor.

CS3500: Object-Oriented Design Fall 2013

CS 211: Methods, Memory, Equality

CS 112 Introduction to Computing II. Wayne Snyder Computer Science Department Boston University

CS 112 Introduction to Computing II. Wayne Snyder Computer Science Department Boston University

C Sc 227 Practice Test 2 Section Leader Your Name 100pts. a. 1D array b. PriorityList<E> c. ArrayPriorityList<E>

Harvard School of Engineering and Applied Sciences CS 152: Programming Languages

Java Fundamentals (II)

Typed Scheme: Scheme with Static Types

Introduction to Programming Using Java (98-388)

CS 220: Introduction to Parallel Computing. Arrays. Lecture 4

Computer Science 1 Ah

Queues. CITS2200 Data Structures and Algorithms. Topic 5

CS 231 Data Structures and Algorithms, Fall 2016

Types, Expressions, and States

Generic Collections. Chapter The Object class

Topic 7: Algebraic Data Types

Object-Oriented Design Lecture 16 CS 3500 Fall 2010 (Pucella) Friday, Nov 12, 2010

SCHEME 8. 1 Introduction. 2 Primitives COMPUTER SCIENCE 61A. March 23, 2017

Implementing a List in Java. CSE 143 Java. Just an Illusion? List Interface (review) Using an Array to Implement a List.

SCHEME 7. 1 Introduction. 2 Primitives COMPUTER SCIENCE 61A. October 29, 2015

6.01, Spring Semester, 2008 Assignment 3, Issued: Tuesday, February 19 1

Type Hierarchy. Comp-303 : Programming Techniques Lecture 9. Alexandre Denault Computer Science McGill University Winter 2004

Soda Machine Laboratory

CS2 Algorithms and Data Structures Note 10. Depth-First Search and Topological Sorting

INTERFACES IN JAVA. Prof. Chris Jermaine

Transcription:

Object-Oriented Design Lecture 11 CS 3500 Spring 2010 (Pucella) Tuesday, Feb 16, 2010 11 Polymorphism The functional iterator interface we have defined last lecture is nice, but it is not very general. As defined, it can only be used to iterate over structures that yield integers. (Because of the definition of the element() method. If we wanted to define an iterator over structures that yields, say, Booleans, or Person objects, then we need to define a new iterator interface for that type. That s suboptimal, to say the least. In particular, this means that we cannot write a function that can use any iterator independently of the type of value it yields. For instance, consider the following function that uses an iterator to count how many elements are in an aggregate structure: public static int countelements (FuncIteratorInt it) { if (it.haselement()) return 1 + countelements(it.movetonext()); else return 0; That works for iterators that return integers. If you wanted to count the number of lines in a sprite (cf., Homework 2), then you would need to write: public static int countelements (FuncIteratorLine it) { if (it.haselement()) return 1 + countelements(it.movetonext()); else return 0; That s silly we need two functions to count elements, and the body of the functions are actually exactly the same, because counting elements does not depend on the type of elements. Can we figure out a nice way to reuse the code for countelements() across two different kind of iterators? 11.1 Polymorphic Interfaces One way to do that is to recognize that the interfaces FuncIteratorInt and FuncIteratorLine are really the same interface that differs only in the type of the elements. What we really 1

want is an interface that is parameterized by the type of result it returns. public interface FuncIterator<T> { public boolean haselement (); public T element (); public FuncIterator<T> movetonext (); Basically, the definition is as before, except for the <T> annotation. This defines interface FuncIterator with a type parameter T. (In Java, this is called a generic interface, but common names are parameterized interface, or polymorphic interface. I tend to favor the latter.) Within FuncIterator<T> you can use T as if it were a type. Note the definition/use distinction: the T in interface FuncIterator<T> is a definition it tells you that T is a parameter that can be instantiated (a bit like the parameter of a function) while every other use of T in the interface is a use it tells you that whatever you choose to instantiate T with will be used to replace those Ts. When it actually comes time to use such an interface, you must instantiate it at the type you require, say FuncIterator<Integer> or FuncIterator<Person>. Intuitively, FuncIterator<Integer> is as if you had written FuncIterator with Integer in place of every T. One restriction we have is that you can only instantiate a parameter at a class type meaning that you cannot write FuncIterator<int>, for example. But we can use the classes corresponding to primitive types, Integer, Boolean, and so on, that are simple wrappers around the primitive types. 1 Before we get to write a single version of countelements() that works for any kind of FuncIterator, let me give you an example of how you can define a class that implement such a parameterized interface. Suppose we wanted to have the List class use the above interface for its iterator. Recall that List is an implementation of integer lists. Here is the resulting implementation: / ABSTRACT CLASS FOR LISTS / public abstract class List { public static List empty () { return new EmptyList(); public static List cons (int i, List l) { return new ConsList(i,l); 1 Java can and will automatically convert between wrapper classes and primitive types, but sometimes we will create such values by hand, using for instance new Integer(i) to create a wrapped integer i of type Integer, and using method intvalue() to get the underlying primtive integer out of an object of class Integer. 2

public abstract boolean isempty (); public abstract int first (); public abstract List rest (); public abstract FuncIterator<Integer> funciterator (); / CONCRETE CLASS FOR EMPTY CREATOR / class EmptyList extends List { public EmptyList () { public boolean isempty () { return true; public int first () { throw new Error ("EmptyList.first()"); public List rest () { throw new Error ("EmptyList.rest()"); public FuncIterator<Integer> funciterator () { return new EmptyFuncIterator(); / ITERATOR FOR EMPTY LISTS / class EmptyFuncIterator implements FuncIterator<Integer> { public EmptyFuncIterator () { public boolean haselement () { return false; 3

public Integer element () { throw new java.util.nosuchelementexception("emptyfunciterator.element()"); public FuncIterator<Integer> movetonext () { throw new java.util.nosuchelementexception("emptyfunciterator.movetonext()"); / CONCRETE CLASS FOR CONS CREATOR / class ConsList extends List { private int firstelement; private List restelements; public ConsList (int f, List r) { firstelement = f; restelements = r; public boolean isempty () { return false; public int first () { return firstelement; public List rest () { return restelements; public FuncIterator<Integer> funciterator () { return new ConsFuncIterator(firstElement, restelements.funciterator()); 4

/ ITERATOR FOR NON EMPTY LISTS / class ConsFuncIterator implements FuncIterator<Integer> { private int currentelement; private FuncIterator<Integer> restiterator; public ConsFuncIterator (int c, FuncIterator<Integer> r) { currentelement = c; restiterator = r; public boolean haselement () { return true; public Integer element () { return new Integer(this.currentElement); public FuncIterator<Integer> movetonext () { return restiterator; In other words, nothing special, aside from the explicit conversion from int to Integer in method element() of class ConsFuncIterator, which is not necessary because Java will perform the conversion automatically, but I m emphasizing here because it will become useful later in the lecture. To use such a parameterized FuncIterator interface, we need to specify exactly how to instantiate the T parameter in the definition. Thus, for instance, we can write the following function that prints all the elements that are supplied by an integer-yielding iterator (written using a while loop): public static void printelements (FuncIterator<Integer> it) { FuncIterator<Integer> temp = it; while (temp.haselement()) { System.out.println ("Element = " + temp.element()); temp = temp.movetonext(); 5

or the function that counts the number of elements supplied by an integer-yielding iterator (written recursively): public static int countelements (FuncIterator<Integer> it) { if (it.haselement()) return 1 + countelements(it.movetonext()); else return 0; Polymorphic interfaces are extensively used in the Java Collections framework. 11.2 Polymorphic Methods Adding parameterization to interfaces is such a natural addition to a language that it hardly seems worth making a big fuss about it. However, from this small addition, a cascade of other changes naturally follow that drastically affect the programming experience. Parameterization for interfaces is a way to reuse code it kept us from having to define multiple interfaces that look the same except for the type of some of their operations. But to maximize code reuse in the presence of polymorphic interfaces, however, we need a bit more than what we have seen until now. Consider the example I gave at the beginning of the lecture, defining a countelements() function that works irrespectively of the kind of functional iterator we have. What is the type of such a function? Intuitively, we would like to say that countelements() takes an argument of type FuncIterator<T> for any class T we want. In pseudo-java, we would express this as: public static for any T int countelements (FuncIterator<T> it) { if (it.haselement()) return 1 + countelements(it.movetonext()); else return 0; In real Java, this is written: public static <T> int countelements (FuncIterator<T> it) { if (it.haselement()) return 1 + countelements(it.movetonext()); else return 0; 6

The signature has that extra <T> at the front, before the return type. This is Java-speak for for any T, it s the indication that the method is polymorphic, and the T in the angle brackets tells you what is the type variable that you are using in the method definition. The <T> here is the definition every other occurrence of T in the function is a use. The T can be used in the type of the result and the arguments to the method, as well as in the body of the method, in case we need to define local variables that depend on that type. For comparison, here is a variant of countelements() that uses a while loop instead note how the use of T in the definition of the local variable temp: public static <T> int countelements (FuncIterator<T> it) { FuncIterator<T> temp = it; int count = 0; while (temp.haselement()) { count = count + 1; temp = temp.movetonext(); return count; When a polymorphic function is called, the type checker tries to figure out what is the right type to instantiate the type variable to. For instance, if you call countelements(v) where v is a variable with compile-time type FuncIterator<Line>, then the type checker figures out that it should call countelements() instantiating the type variable T to Line, and type check the code accordingly. A polymorphic function doesn t need to use polymorphic interfaces. Consider the problem of writing a single identity function that works for any class. Intuitively, the identity function takes an argument x and just returns x, without doing anything with it. Such a function should be able to take an x of any type T, and return that x, thus with result type the same T. We can write this using a single polymorphic function: public static <T> T identity (T val) { return x; As an exercise, write a single printelements() function that prints all the elements from an iterator, irrespectively of the kind of iterator is given as an argument. The above examples illustrate that polymorphic methods are a way to reuse client code, by only requiring you to write a single client method that works with multiple (possibly unrelated) classes, including those implementing polymorphic interfaces. 7

Bounded Polymorphism Let s look at a slightly more complex example. Suppose we write an function that sums all the elements given by a functional iterator, something like: public static int sumelements (FuncIterator<Integer> it) { if (it.haselement()) return it.element() + sumelements(it.movetonext()); else return 0; (Note that there are implicit conversions between ints and Integers in this code.) Clearly, this functions cannot work for all iterators, because not all classes that an iterator can yield have a notion of addition defined on it it doesn t always make sense to add all elements that an iterator yields. But it should work for all iterators that yield values for a type that has a notion of addition. So can we get that kind of code reuse? Yes, using something called bounded polymorphism. Basically, bounded polymorphism lets us say for all types that satisfy a certain property, where that property is expressed using subclassing. So let s first try to figure out how to express the property has a notion of addition. One way to do that is to define a class with an operation add, and since any subclass of that class will be required to have such an operation, that can be taken to mean that it supports addition. We ll make that class an interface. (Why?) public interface Addable<T> { T add (T val); A class subclassing Addable<T> says that it can add an element of type T to get element of type T. Now, Integers do not implement Addable, but we can define a notion of addable integers easily enough: public class AInteger implements Addable<AInteger> { private int intvalue; private AInteger (int i) { intvalue = i; public static AInteger create (int i) { 8

return new AInteger(i); public int intvalue () { return this.intvalue; public AInteger add (AInteger val) { return new AInteger(this.intValue()+val.intValue()); public String tostring () { return (new Integer(this.intValue())).toString(); (I should define equals() and hashcode() methods as well, but I leave them as an exercise.) Let s modify our List implementation so that it uses AIntegers instead of Integers: / ABSTRACT CLASS FOR LISTS / public abstract class List { // no java constructor for this class, it is abstract public static List empty () { return new EmptyList(); public static List cons (int i, List l) { return new ConsList(i,l); public abstract boolean isempty (); public abstract int first (); public abstract List rest (); public abstract FuncIterator<AInteger> funciterator (); 9

/ CONCRETE CLASS FOR EMPTY CREATOR / class EmptyList extends List { public EmptyList () { public boolean isempty () { return true; public int first () { throw new Error ("first() on an empty list"); public List rest () { throw new Error ("rest() on an empty list"); public FuncIterator<AInteger> funciterator () { return new EmptyFuncIterator(); / ITERATOR FOR EMPTY LISTS / class EmptyFuncIterator implements FuncIterator<AInteger> { public EmptyFuncIterator () { public boolean haselement () { return false; public AInteger element () { throw new java.util.nosuchelementexception("emptyfunciterator.element()"); public FuncIterator<AInteger> movetonext () { throw new java.util.nosuchelementexception("emptyfunciterator.movetonext()"); 10

/ CONCRETE CLASS FOR CONS CREATOR / class ConsList extends List { private int firstelement; private List restelements; public ConsList (int f, List r) { firstelement = f; restelements = r; public boolean isempty () { return false; public int first () { return firstelement; public List rest () { return restelements; public FuncIterator<AInteger> funciterator () { return new ConsFuncIterator(firstElement, restelements.funciterator()); / ITERATOR FOR NON EMPTY LISTS / class ConsFuncIterator implements FuncIterator<AInteger> { private int currentelement; private FuncIterator<AInteger> restiterator; public ConsFuncIterator (int c, FuncIterator<AInteger> r) { currentelement = c; restiterator = r; public boolean haselement () { 11

return true; public AInteger element () { return AInteger.create(currentElement); public FuncIterator<AInteger> movetonext () { return restiterator; I can write a bounded polymorphic function sumelements that sums all the elements given by an iterator as long as the type of values that the iterator yields is a subclass of Addable, e.g., has an add() operation: public static <T extends Addable<T>> T sumelements (T init, FuncIterator<T> it) { if (it.haselement()) return it.element().add(sumelements(init,it.movetonext())); else return init; Note that parameterization <T extends Addable<T>>, read for all classes T that are subclasses of Addable<T>, 2 that is: for all classes T that have an operation add that take elements of type T (same type!) yielding elements of type T, which is exactly what we want. Question: why do we need to supply an initial value of type T? Now we can write, for example: List sample = List.cons(1, List.cons(2, List.cons(3, List.empty()))); AInteger zero = AInteger.create(0); AInteger sum = sumelements(zero, sample.funciterator()); System.out.println("Sum = " + sum); and get Sum = 6 2 extends in this particular context really means is a subclass of it matches both a class that extends another class, and a class that implements an interface. 12

as output. To illustrate the usefulness of this, it would be nice to have another subclass of Addable<T>, so we can reuse the above code. Let s define pairs of integers, with an add operation that is just vector addition: (a, b) + (c, d) is just (a + c, b + d): public class PairAI implements Addable<PairAI> { private AInteger first; private AInteger second; private PairAI (AInteger f, AInteger s) { this.first = f; this.second = s; public static PairAI create (AInteger f, AInteger s) { return new PairAI(f,s); public AInteger first () { return this.first; public AInteger second () { return this.second; public String tostring () { return "(" + this.first().tostring() + "," + this.second().tostring() + ")"; public PairAI add (PairAI p) { return create(this.first().add(p.first()), this.second().add(p.second())); Let s define an ADT for lists of pairs of integers, ListPairAI. The signature is the expected one: public static ListPairAI empty (); public static ListPairAI cons (PairAI, ListPairAI); 13

public boolean isempty (); public PairAI first (); public ListPairAI rest (); public String tostring (); public FuncIterator<PairAI> funciterator (); The implementation is obtained readily from the Specification design pattern, and some experience writing functional iterators for lists: / ABSTRACT CLASS FOR LISTS / public abstract class ListPairAI { public static ListPairAI empty () { return new EmptyListPairAI(); public static ListPairAI cons (PairAI p, ListPairAI l) { return new ConsListPairAI(p,l); public abstract boolean isempty (); public abstract PairAI first (); public abstract ListPairAI rest (); public abstract FuncIterator<PairAI> funciterator (); / CONCRETE CLASS FOR EMPTY CREATOR / class EmptyListPairAI extends ListPairAI { public EmptyListPairAI () { public boolean isempty () { return true; public PairAI first () { throw new Error ("EmptyListPairAI.first()"); 14

public ListPairAI rest () { throw new Error ("EmptyListPairAI.rest()"); public FuncIterator<PairAI> funciterator () { return new EmptyPAIFuncIterator(); / ITERATOR FOR EMPTY LISTS / class EmptyPAIFuncIterator implements FuncIterator<PairAI> { public EmptyPAIFuncIterator () { public boolean haselement () { return false; public PairAI element () { throw new java.util.nosuchelementexception("emptypaifunciterator.element()"); public FuncIterator<PairAI> movetonext () { throw new java.util.nosuchelementexception("emptypaifunciterator.movetonext()"); / CONCRETE CLASS FOR CONS CREATOR / class ConsListPairAI extends ListPairAI { private PairAI firstelement; private ListPairAI restelements; public ConsListPairAI (PairAI f, ListPairAI r) { firstelement = f; restelements = r; 15

public boolean isempty () { return false; public PairAI first () { return firstelement; public ListPairAI rest () { return restelements; public FuncIterator<PairAI> funciterator () { return new ConsPAIFuncIterator(firstElement, restelements.funciterator()); / ITERATOR FOR NON EMPTY LISTS / class ConsPAIFuncIterator implements FuncIterator<PairAI> { private PairAI currentelement; private FuncIterator<PairAI> restiterator; public ConsPAIFuncIterator (PairAI c, FuncIterator<PairAI> r) { currentelement = c; restiterator = r; public boolean haselement () { return true; public PairAI element () { return currentelement; public FuncIterator<PairAI> movetonext () { return restiterator; 16

We can check that we can reuse both printelements() and sumelements(), as expected: PairAI p1 = PairAI.create(AInteger.create(1),AInteger.create(2)); PairAI p2 = PairAI.create(AInteger.create(3),AInteger.create(4)); PairAI p3 = PairAI.create(AInteger.create(5),AInteger.create(6)); ListPairAI sample = ListPairAI.cons(p1, ListPairAI.cons(p2, ListPairAI.cons(p3, ListPairAI.empty()))); printelements(sample.funciterator()); PairAI zero2 = PairAI.create(AInteger.create(0),AInteger.create(0)); PairAI sum = sumelements(zero2, sample.funciterator()); System.out.println("Sum = " + sum); with output: Element = (1,2) Element = (3,4) Element = (5,6) Sum = (9,12) Voilà. Code reused. 17