On this page:
Getting Started
Introduction – Tutorial with filter
Abstracting with Function Objects
Additional practice
6.8

Lab 6: Abstracting with Function Objects

Goals: The goal of this lab is to learn how to design and use function objects - Java’s way of passing functions as arguments to methods.

Related files:
  tester.jar     Lab6.zip  

Getting Started

Download the files in Lab6.zip. The folder contains the files:

ImageFile.java

ILoIF.java

MTLoIF.java

ConsLoIF.java

ExamplesImageFile.java

Diagram.txt

We give you partially defined classes and examples so you can quickly skip typing what you already know, and focus on the new material. If any of the pre-supplied code is unfamiliar to you, then be sure to read through it before beginning the rest of the lab!

Create a new project Lab6-FunctionObjects and import into it all of the given .java files. Also add tester.jar to the Java classpath.

Introduction – Tutorial with filter

Let’s start with three methods that deal with lists of files: filterSmallerThan40000, filterNamesShorterThan4, countSmallerThan40000. Each of them should do exactly what their names suggest.

Do Now!

Design these three methods on ILoIF. They should be very similar...

Look at the first two methods. They should only differ in the body of the conditional in the class ConsListImage. The two versions look like this:

if (this.first.size() < 40000)
if (this.first.name.length() < 4)

Both represent a boolean expression that depends only on the value of this.first. Think about the filter loop function in DrRacket. Its contract and header were:

;; filter: (X -> boolean) [Listof X] -> [Listof X]
;; to construct a list from all those items
;; in alox for which p holds
(define (filter p alox)...)

The argument p was a function/predicate that consumed an item from the list (for example the first) and produced a boolean value that indicated whether the item is acceptable.

Java does not allow us to use functions or methods as arguments. To get around this problem we need to go through several steps:

Define an interface that contains as its only method the header for the desired predicate: we define the interface ISelectImageFile:

// to represent a predicate for ImageFile-s interface ISelectImageFile{
 
// Return true if the given ImageFile // should be selected boolean apply(ImageFile f);
}

Now any class that implements this interface will necessarily implement this predicate method. Suppose we try to design an abstracted filter method that consumes an object of the type ISelectImageFile as follows:

// produce a list of ImageFiles from this list // that satisfy the given predicate filter(ISelectImageFile pick);

Inside the implementations of filter (in ConsLoIF and MtLoIF) our template now includes

... pick.apply(ImageFile) ... -- boolean

Let’s hang on to that observation for a moment. In order to use it, we now need to define a class that implements this interface. Let’s implement a class that would be useful for filterSmallerThan40000: it needs to define the method apply that consumes an instance of ImageFile and returns true if the size of the given object is less than 40000. The following class definition accomplishes this task:

/* Select image files smaller than 40000 */
class SmallImageFile implements ISelectImageFile {
 
/* Return true if the given ImageFile is smaller than 40000 */
public boolean apply(ImageFile f) {
return f.height * f.width < 40000;
}
}

We haven’t implemented filter itself yet; we’re writing the tests first.

In the ExamplesImageFile class we can now invoke the filter method on an IListImageFile with an instance of SmallImageFile as the argument:

IListImageFile mtImagelist = new MTListImageFile();
IListImageFile imagelist = .....
IListImageFile smallImagelist = ...
 
ISelectImageFile smallFiles = new SmallImageFile();
 
// test the method filter on small image files boolean testFilter(Tester t){
return
t.checkExpect(mtImagelist.filter(this.smallFiles),
this.mtImagelist) &&
t.checkExpect(imageList.filter(this.smallFiles),
this.smallImagelist);
}

Turning back to the implementations of filter, we observed above that we can compute

pick.apply(this.first)

If our pick object happens to be an instance of SmallImageFile, then this expression will select the ImageFile objects for which the size is smaller than 40000.

Do Now!

Which language mechanism of object-oriented programming ensures this for us?

Make sure you view the diagram using a fixed-width font.

The file Diagram.txt shows the class diagram for these classes and interfaces. It introduces a dotted line to indicate that the argument for a method is an instance of another class or interface.

Abstracting with Function Objects

The only purpose for defining the class SmallImageFile is to implement one method that determines whether the given ImageFile object has the desired property (a predicate method). An instance of this class can then be used as an argument to a method that deals with ImageFiles.

Finish the work at home.

Exercise

Food for thought: How would this program be different if we have instead worked with lists of Books? What about lists of IShapes?

Additional practice