CSE190L Notes for Friday, 3/30/07

I continued the example from Wednesday's lecture. We were working on a class called Foo that was supposed to have behavior similar to the String class. Here's where we left off:

        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);
            }
        
            public int compareTo(Foo other) {
                return text.compareTo(other.text);
            }

            public String toString() {
                return text;
            }
	}        
We were trying to get this client code to properly build up a list of the unique strings three different ways:

        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);
            }
        }
We managed to get the TreeMap to work by implementing the compareTo method. We were trying to get the ArrayList call on contains to work by overriding the equals method, but we used the wrong parameter type. The built-in equals method takes a parameter of type Object. So we had to rewrite the equals method in the Foo class as follows:

            public boolean equals(Object o) {
                Foo other = (Foo) o;
                return text.equals(other.text);
            }
Once we made this change, the ArrayList correctly ignored duplicates and we got this output:

        [to, to, be, not, or, be]
        [be, not, or, to]
        [to, be, or, not]
So even though we have properly defined both compareTo and equals, the HashSet is introducing duplicates when it shouldn't. Why would that be? The answer is that there is a contract between equals and hashCode that must be maintained for classes like HashSet to work properly. This is described in the Java api documentation for the equals method. It's also Item 8 in Joshua Bloch's 57 tips from Effective Java: "Always override hashCode when you override equals." So we added this method to the Foo class:

            public int hashCode() {
                return text.hashCode();
            }
Once we did that, we found that our client code worked and produced this output:

        [to, or, be, not]
        [be, not, or, to]
        [to, be, or, not]
The key point about all of this is to understand the role of these different standard methods: toString, equals, hashCode, compareTo. Depending upon how you intend to use the object, you might need to override all of these methods to be able to use them in conjunction with standard Java classes like ArrayList, TreeSet and HashSet. Below is the complete final version of our Foo class:

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

            public boolean equals(Object o) {
                Foo other = (Foo) o;
                return text.equals(other.text);
            }
        
            public int hashCode() {
                return text.hashCode();
            }

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

            public String toString() {
                return text;
            }
	}        
We then spent some time beginning chapter 7 of the textbook. The chapter begins with a discussion of the history of Swing and AWT and I added a few comments of my own. The AWT philosophy was to use what are known as "peer" components. So an AWT Frame object would be paired up with a native Frame object on whatever system Java was running on. This didn't end up working very well. What was supposed to be "Write once, run everywhere" became "Write once, debug everywhere."

And Sun kept changing things. In release 1.1 of Java, Sun decided to change the model of how user interface components are defined. Many standard methods were now considered "deprecated" (discouraged, not to be used any more), which meant that people had to rewrite their code and learn a new way to do things.

Then a group at Netscape came up with a set of tools that didn't depend on peer components. Those tools eventually became what we know as the Swing library. Sun decided to make it part of standard Java, but they didn't want to throw out all of AWT because it probably would have angered many Java programmers who were already annoyed with the changes that came with version 1.1. So Sun decided to graft Swing on top of AWT. This has led to some odd naming conventions. AWT has frames and panels and buttons but so does Swing. So Sun decided to put a "J" in front of the Swing component names. AWT has a Frame, Panel and Button class while Swing has a JFrame, JPanel and JButton class. But they are also mixed together. A typical Swing program uses a mixture of AWT and Swing components.

One of the most important classes we'll use is called JFrame. We found that constructing a JFrame isn't enough to make it do something. You have to call the setVisible method of the JFrame passing the value true to make it pop up on the screen.

We didn't have time for much more, so I said we'd continue the discussion in Monday's lecture.


Stuart Reges
Last modified: Fri Apr 6 09:20:04 PDT 2007