CSE143 Notes for Monday, 4/19/10

We started a new topic: interfaces. I used jGRASP so that we could play with some code. I pointed out that I had available to us the ArrayIntList class that we talked about in the first week of the quarter and the LinkedIntList class that we discussed in section. These two classes have very similar methods. They each have:

This isn't an accident. When we got to linked lists, we purposely implemented new versions of these methods that worked for linked lists. The point is that these classes are similar in terms of what they can do by they are very different in how they do it.

To underscore the similarity, I wrote the following client code that does parallel operations on two different lists, adding three values, removing one, and printing it before and after the remove:

        public class ListClient {
            public static void main(String[] args) {
                ArrayIntList list1 = new ArrayIntList();
                list1.add(18);
                list1.add(27);
		list1.add(93);
		System.out.println(list1);
		list1.remove(1);
                System.out.println(list1);

                LinkedIntList list2 = new LinkedIntList();
                list2.add(18);
                list2.add(27);
		list2.add(93);
		System.out.println(list2);
		list2.remove(1);
                System.out.println(list2);
            }
        }
The program produced the following output:

        [18, 27, 93]
        [18, 93]
        [18, 27, 93]
        [18, 93]
As expected, the two kinds of list behave the same way. I pointed out that in CSE142 we tried to emphasize the idea that you shouldn't have redundant code like this. So ideally we'd like to move this code into a method. As a first attempt, I said:

        public class ListClient {
            public static void main(String[] args) {
                ArrayIntList list1 = new ArrayIntList();
                processList(list1);

                LinkedIntList list2 = new LinkedIntList();
                processList(list2);
            }

            public static void processList(ArrayIntList list) {
                list.add(18);
                list.add(27);
		list.add(93);
		System.out.println(list);
		list.remove(1);
                System.out.println(list);
            }
        }
This is obviously a better way to write this program, but it didn't compile. jGRASP highlighted the second call on processList and said:

        File: /Users/reges/143/ListClient.java  [line: 4]
        Error: processList(ArrayIntList) in ListClient cannot be applied to (LinkedIntList)
The error indicates that the method processList takes an ArrayIntList as an argument and that it cannot be applied to a call that passes a LinkedIntList as a parameter. So I tried changing the parameter type to LinkedIntList and then it produced an error for the other call.

The point is that we want to be able to think of these lists as being the same thing. In computer science we try to use abstraction to find what is common between these two classes even though we recognize that there are things that are quite different about the two. We would imagine an "integer list" abstraction of which these are two possible implementations. They're both the same in the sense that they provide basic "integer list" functionality like an appending add. But they are different in the sense that they are implemented quite differently (one using an array and the other using a linked list).

With Java, we have actual language support for this concept. Not only can we talk abstractly about an "integer list" abstraction, we can actually define it using what is known in Java as an "interface".

In an interface, we want to specify that certain behaviors exist without saying how they are implemented. We want to specify the "what" part without specifying the "how" part. So I went to the ArrayIntList class and deleted all of its comments and all of the method bodies. That left me with this:

        public class ArrayIntList {
            public int size()
            public int get(int index)
            public String toString()
            public int indexOf(int value)
            public void add(int value)
            public void add(int index, int value)
            public void remove(int index)
        }
To turn this into an interface, I had to change the name to something new. I decided to call it IntList. I also had to change the word "class" to "interface". The method have no curly braces because I deleted those lines of code. I replaced each of those with a semicolon. That left me with:

        public interface IntList {
            public int size();
            public int get(int index);
            public String toString();
            public int indexOf(int value);
            public void add(int value);
            public void add(int index, int value);
            public void remove(int index);
        }
This is how you define an interface. In place of the method bodies, we have a semicolon to indicate, "The implementation isn't given." You can think of an interface as being like a hollow radio. It has all of the knobs and buttons that are used to control the radio, but it has none of the "innards" that make it work.

So I went back to our code and changed the header for the processList method to use the interface instead:

        public static void processList(IntList list) {
Unfortunately, this led to two errors. Both of the calls now failed with messages like this:

        File: /Users/reges/143/ListClient.java  [line: 4]
        Error: processList(IntList) in ListClient cannot be applied to (ArrayIntList)
That seems a bit odd, because both ArrayIntList and LinkedIntList have the methods mentioned in the IntList interface. The explanation is that Java requires classes to explicitly state what interfaces they implement. So we had to modify the two classes to include this notation:

        public class ArrayIntList implements IntList {
             ...
        }
        
        public class LinkedIntList implements IntList {
             ...
        }
With this change, the code compiled and executed properly.

I then tried creating an instance of the IntList class:

        IntList list = new IntList();
This produced an error. Interfaces cannot be instantiated because they are incomplete.

I then asked people to think about the types of these objects. Consider our variable list1 from main:

        ArrayIntList list1 = new ArrayIntList();
This describes an object of type ArrayIntList, but it also describes an object of type IntList. That's why the method call works. The object is of more than one type. This idea is important in object oriented programming because Java objects can typically fill many roles. This is related to the notion of type because each role is defined by a new type. Interfaces are a way to define a new role, a new type. By saying that ArrayIntList and LinkedIntList both implement the IntList interface, we say that they both fill that role. So given an ArrayIntList, we can say that it is of type ArrayIntList, but we can also say that it is of type IntList. Similarly, a LinkedIntList is of type LinkedIntList and of type IntList.

It is a good idea to use interfaces when defining the types of variables. I changed our main method to use type intList for the variables instead of listing the individual types:

        public static void main(String[] args) {
            IntList list1 = new ArrayIntList();
            processList(list1);

            IntList list2 = new LinkedIntList();
            processList(list2);
        }
These variables are more flexible than the old variables. The variable list1, for example, can now refer to any IntList object, not just one of type ArrayIntList. In fact, list1 can even store a reference to other kinds of objects, as long as they implement the IntList interface.

I then mentioned that this is an idea that has been used throughout the collections classes in Java (the java.util package). This idea is stressed by Joshua Bloch, the author of a book called Effective Java, which I said was one of the most useful books I've read about Java. Joshua Bloch was the primary architect of the collections framework and has influenced much of Sun's work. He now works at Google.

In the collections framework, Bloch was careful to define data structure abstractions with interfaces. For example, there are interfaces for List, Set and Map which are abstractions that we'll be discussing this quarter (we've already been examining the List concept). In addition to the interfaces, there are various implementations of each. For example, ArrayList and LinkedList both implement the List interface. TreeMap and HashMap both implement the Map interface. And TreeSet and HashSet both implement the Set interface.

Bloch's book is written as a series of 78 suggested practices for Java programmers. I have a link to the book and the suggested practices from our class web page (under "useful links"). His item 52 is to "Refer to objects by their interfaces." He says, "you should favor the use of interfaces rather than classes to refer to objects. If appropriate interface types exist, parameters, return values, variables and fields should all be declared using interface types." This last sentence was in bold face in the book, indicating how important Bloch thinks this is, and I've reproduced that here. He goes on to say that, "The only time you really need to refer to an object's class is when you're creating it."

We have been using this concept for many years in programming. It is traditionally referred to as an Abstract Data Type or ADT:

        A bstract
        D ata
        T ype
The List, Set and Map abstractions from the collections framework are all ADTs.

I also mentioned that the best analogy I have for interfaces is that they are similar to how we use the concept of certification. You can't claim to be a certified doctor unless you have been trained to do certain specific tasks. Similarly, to be a certified teacher you have to know how to behave like a teacher, to be a certified nurse you have to know how to behave like a nurse, and so on. In Java, if you want to claim to be a certified IntList object, then you have to have several different methods, including an appending add method . Java classes are allowed to implement as many interfaces as they want to, just as a single person might be certified to do several jobs (e.g., both a certified doctor and a certified lawyer). When there are more than one, you list them separated by commas after the word "implements".

In computer science, two of the most fundamental ADTs are called stacks and queues. They are so simple that they almost seem not worth studying. They are like the programming equivalent of drawers and shelves. Drawers and shelves are very simple and, therefore, sort of boring, and yet we find uses for them everywhere we turn.

It is useful to study stacks and queues as a way to understand a minimal kind of data structure. We'll find, for example, that they are less powerful than the list structures we have been looking at. But we often find ourselves wanting to think in terms of the simplest possible solution to a problem, as in, "You could solve that with a stack."

Like lists, stacks and queues store an ordered sequence of values. A minimal set of operations for such a structure would require at least:

These three operations are the bare bones that you'd need for such a structure and in their purest form, stacks and queues have just these three operations. I have put together a version of these that also includes a size method that lets you ask for the number of elements in the structure.

Stacks and queues are similar in that they each store a sequence of values in a particular order. But stacks are what we call LIFO structures while queues are FIFO structures:

        stacks        queues

        L-ast         F-irst
        I-n           I-n
        F-irst        F-irst
        O-ut          O-ut
The analogy for stacks is to think of a cafeteria and how trays are stacked up. When you go to get a tray, you take the one on the top of the stack. You don't bother to try to get the one on the bottom, because you'd have to move a lot of trays to get to it. Similarly if someone brings clean trays to add to the stack, they are added on the top rather than on the bottom. The result is that stacks tend to reverse things. Each new value goes to the top of the stack, and when we take them back out, we draw from the top, so they come back out in reverse order.

The analogy for queues is to think about standing in line at the grocery store. As new people arrive, they are told to go to the back of the line. When the store is ready to help another customer, the person at the front of the line is helped. In fact, the British use the word "queue" the way we use the word "line" telling people to "queue up" or to "go to the back of the queue".

In the case of a stack, the adding operation is called "push" and the removing operation is called "pop". All operations occur at one end of the stack, at the top. We push values onto the top and we pop them off the top. There is also a method for testing whether the stack is empty and an operation for requesting the current size of the stack. I showed people an interface with these four operations:

        public interface Stack<E> {
            public void push(E value);
            public E pop();
            public boolean isEmpty();
            public int size();
        }
Notice that we are using Java generics to define the Stack in terms of an unspecified element type E. That way we'll be able to have a Stack<String> or Stack<Integer> or a Stack of any other kind of element type we are interested in.

For queues, we have a corresponding set of operations but they have different names. When values go into a queue we refer to it as "enqueueing" a value. When values are removed from a queue we refer to it as "dequeueing" a value. So the Queue interface looks like this:

        public interface Queue<E> {
            public void enqueue(E value);
            public E dequeue();
            public boolean isEmpty();
            public int size();
         }
We looked at a simple client program that uses an array of String data to initialize a stack and a queue and to print their contents:

        public class Mon {
            public static void main(String[] args) {
        	String[] data = {"four", "score", "and", "seven", "years", "ago"};
        	Queue<String> q = new LinkedQueue<String>();
        	Stack<String> s = new ArrayStack<String>();
        	
        	for (String str : data) {
        	    q.enqueue(str);
        	    s.push(str);
        	}
        	
        	System.out.println(q);
        	System.out.println(s);
            }
        }
It produced the following output:

        front [four, score, and, seven, years, ago] back
        bottom [four, score, and, seven, years, ago] top
I pointed out that the toString method of each class includes text that indicates the front/back of the queue and the top/bottom of the stack. I said that we would continue the discussion of stacks and queues in Wednesday's lecure.


Stuart Reges
Last modified: Wed Apr 21 08:40:35 PDT 2010