CSE190L Notes for Wednesday, 3/28/07

I mentioned a general outline for how we are going to work through the Core java book:

I then spent some time reviewing the concept of static versus nonstatic elements of a class. I pointed out that in languages like C and C++, you can have free standing methods. For example, in C you can write a main method this way:

        void main() {
            ...
        }
And there is a sqrt method available from a library that looks like this:

        double sqrt(double n) {
            ...
        }
Many of these free standing methods were easy to turn into instance methods. For example, if you want a substring of a string, it makes sense to talk to a particular string object and ask it for its substring. But what object would you talk to for the main method or to find a square root? It would be silly to have to define a Math object just to get a square root.

The compromise in Java was to introduce the static keyword and to associate each of these methods with a specific class. And once it was inside a class, we had to decide what kind of access to give people outside the class (public? private?), so suddenly our headers look like this:

        public static void main(String[]) {
            ...
        }

        public static double sqrt(double n) {
            ...
        }
But these methods are very similar to the free standing methods in C and C++. It's just that in C they are the norm whereas in Java they are the exception.

I showed this class as an example of static methods and fields:

public class Borg { private static int ourCount; public Borg() { ourCount++; myCount = ourCount; } public static void speak() { System.out.println("We are the borg"); System.out.println("Resistance is futile"); } public static void howMany() { System.out.println("There are " + ourCount + " of us"); } } Remember that static elements are associated with the class (just one of each element), whereas there are many different copies of each nonstatic element (one for each instance of the class). For example, we were able to call the methods without any instances at all:

public class BorgTest { public static void main(String[] args) { Borg.speak(); Borg.howMany(); } } which produced this output:

        We are the borg
        Resistance is futile
        There are 0 of us
This got more interesting when we created some instances and had them call the methods:

        We are the borg
        Resistance is futile
        There are 5 of us
        
        We are the borg
        Resistance is futile
        There are 5 of us
        
        We are the borg
        Resistance is futile
        There are 5 of us
        
        We are the borg
        Resistance is futile
        There are 5 of us
        
        We are the borg
        Resistance is futile
        There are 5 of us
        
        We are the borg
        Resistance is futile
        There are 5 of us
The most interesting thing here is the static counter. There is just one counter for the entire class, so it is keeping track of the total number of Borg objects every constructed. Of course, because all of the methods are static, they all behave exactly the same way no matter which instance is calling the method (one method shared by all instances).

As a contrast, I added a nonstatic field to keep track of a count for an individual Borg along with a new method to identify that Borg:

        public class Borg {
	    private static int ourCount;
	    private int myCount;
    
	    public Borg() {
		ourCount++;
		myCount = ourCount;
	    }
    
	    public void identify() {
		System.out.println("My designation is " + myCount + " of "
				   + ourCount);
	    }

            ...
        }
We found that we couldn't call the identify method using the class name as we could with the static methods:

        Borg.identify();  // error--requires an instance of Borg
We were able to add this line of code to our loop to have each individual Borg identify itself:

        b.identify();
This produced lines of output like the following:

        My designation is 1 of 5
We then spent some time reviewing inheritance. First we talked about compatibility of different types. In 143 I used the analogy of "roles" (what roles can an object fill?). In section we looked at a problem where you have to figure out when it's legal to say:

        Foo x = new Bar();
This line of code works when a Bar object "is a" Foo (can fill the Foo role). There is a built-in operator in Java called "instanceof" that allows you to ask about this. In terms of "instanceof", we'd say that the line of code above compiles if and only if:

        Bar instanceof Foo
I used ArrayList as an example and we explored the different roles that an ArrayList can fill. We defined this variable:

        ArrayList<String> lst = new ArrayList<String>();
and then we looked for expressions of this form that evaluated to true:

        lst instanceof ArrayList
A first set of them came from the inheritance hierarchy (an ArrayList is an instance of every class going up the chain):

        lst instanceof ArrayList
        lst instanceof AbstractList
        lst instanceof AbstractCollection
        lst instanceof Object
Others came from the interfaces that ArrayList explicitly implements. We found these by looking at the header for ArrayList:

        public class ArrayList<E>
            extends AbstractList<E>
            implements List<E>, RandomAcces, Cloneable, Serializable
So this told us that the following would be true:

        lst instanceof List
        lst instanceof RandomAccess
        lst instanceof Cloneable
        lst instanceof Serializable
But there were even more types for us to discover. ArrayList implements all of these interfaces, but it also implements any interface implemented by the class it extends. So we had to go up the inheritance hierarchy to discover this extra interface that ArrayList implements:

        lst instanceof Collection
Even that wasn't everything. It took a while for someone to figure this out, but someone finally pointed out that interfaces can extend other interfaces. So it's possible that one of the interfaces implemented by ArrayList extends some other interface. By checking this out, we found that the Collection interfaces extends the Iterable interface, so it's also true that:

        lst instanceof Iterable
This completed our list. The documentation for ArrayList includes a list of the inheritance hierarchy along with all of these implemented interfaces.

Then I started an example to explore some of the built-in features of Java like the equals method. I said that the String class has been designed so that it works very well with Java. To explore how it works, I decided to create a variation of the String class (what you might call a "thin wrapper" around a String) called Foo:

        public class Foo {
            private String text;
        
            public Foo(String text) {
                this.text = text;
            }
	}        
I wrote some client code that would manipulate these Foo objects. Initially it just constructed several and put them into an ArrayList:

        import java.util.*;
        
        public class Bar {
            public static void main(String[] args) {
                String[] data = {"to", "be", "or", "not", "to", "be"};
                ArrayList<Foo> lst = new ArrayList<Foo>();
                for (String s : data) {
                    Foo f = new Foo(s);
		    lst.add(f);
                }
                System.out.println(lst);
            }
        }
When we ran it, we got output that wasn't very readable:

        [Foo@192d342, Foo@6b97fd, Foo@1c78e57, Foo@5224ee, Foo@f6a746, Foo@15ff48b]
The problem is that the Object class has a method called toString that isn't normally what we want. That's the method being called above. It indicates the name of the class followed by an at-sign followed by a hexadecimal number that indicates where the object is stored in memory.

We fixed this by adding a toString method to the Foo class:

        public class Foo {
            private String text;
        
            public Foo(String text) {
                this.text = text;
            }

            public String toString() {
                return text;
            }
	}        
Then when we ran the client code again, we got this output:

        [to, be, or, not, to, be]
I said that I wanted to explore how to make a set of these values. There is a Set interface in java.util that represents the set Abstract Data Type (ADT). Sets are unordered collections of values that have no duplicates. The ArrayList allows duplicates, but I wrote some code that calls the contains method of the ArrayList to prevent it from adding any duplicates to the list. Here is the new version of the client code:

        import java.util.*;
        
        public class Bar {
            public static void main(String[] args) {
                String[] data = {"to", "be", "or", "not", "to", "be"};
                Set<Foo> s1 = new HashSet<Foo>();
                Set<Foo> s2 = new TreeSet<Foo>();
                ArrayList<Foo> lst = new ArrayList<Foo>();
                for (String s : data) {
                    Foo f = new Foo(s);
                    s1.add(f);
                    s2.add(f);
        
                    if (!lst.contains(f))
                        lst.add(f);
                }
                System.out.println(s1);
                System.out.println(s2);
                System.out.println(lst);
            }
        }
This code compiled, but when we ran it we got a ClassCastException. The problem is that to be able to use a TreeSet for your values, your values have to implement the Comparable interface. So we had to modify the Foo class to do that:

        public class Foo implements Comparable<Foo> {
            private String text;
        
            public Foo(String text) {
                this.text = text;
            }

            public int compareTo(Foo other) {
                return text.compareTo(other.text);
            }

            ...
	}
This produced the following output:

        [to, to, be, not, or, be]
        [be, not, or, to]
        [to, be, or, not, to, be]
So now the TreeSet version is working, but neither of the others are working. I asked people how the contains method works. How does an ArrayList figure out whether a value is in the list? The answer is that it calls the equals method. So to make it work properly, we'd have to override equals. So I added this to our class:

        public class Foo implements Comparable<Foo> {
            private String text;
        
            public Foo(String text) {
                this.text = text;
            }

            public boolean equals(Foo other) {
                return text.equals(other.text);
            }
But this behaved just as badly as before. Why? Because this isn't the right way to override the equals method. In the case of the Comparable interface, we're implementing a generic interface where compareTo should have a parameter of type Foo. But in this case, we're overriding a method that comes from the Object class. Because it's defined at that top level, it takes a parameter of type Object. The method we have added has a different signature. As a result, we haven't overridden the built-in method, we've introduced an extra equals method that is never called. You can verify this by including a println statement in the method which you'll find is never executed.


Stuart Reges
Last modified: Thu Apr 5 09:16:43 PDT 2007