Chapter 10
Test Your Thinking
Building your own classes is what being a Java programmer is all about. Lot's of people can code, but not as many can engineer great classes. The planning and design of a class can be more important than the code. As with all things, skill comes from practice, so here are some exercises to give you that practice.
1) Create classes to manage baseball players and their batting averages. You'll need classes that associate a name with a batting average, as well as the ability to store multiple records.
Answer:
The key here is to break up the problem into well-defined objects. Once you have the definitions of the objects, you can write a class to implement that object.
As we did earlier in the book, it is possible to store the data in the predefined Java types, like int and Vector. And while we still do use these types "under the hood," creating new classes lets us think in terms of the new objects like Name and BattingAverage. This way we are freeing our minds to think at a higher level.
For our answer, we created two new classes: the Name class and the BattingAverage class. Actually, we really just augmented the two examples from the book.
Click here to see the Name.java source code.
Click here to see the BattingAverage.java source code.
These newer versions of these classes are more realistic than in the book. The code in the links above is more like what you would find in an actual engineering environment. Please note the following additions and changes:
Internal data is now private
One difference is that fields such as firstName and lastName are now marked as private. The only way to externally set or get these values is to use the appropriate "set" or "get" method. While this might appear to be a little bit cumbersome at first, it is really an important part of object-oriented program design. The abstraction of the first and last names lets you focus on the Name object instead of its inner workings.
Javadoc Comments and Documentation
You should also notice that there are quite a few additional comments. While some of the comments are just to be extra informative, the rest of the comments actually server a greater purpose. Hopefully by now, you have used the Javadoc API documentation Web pages that came with your Java 2 SDK software. The Javadocs are easily the most important reference that you'll ever have for Java programming. Did you ever wonder where they came from? The truth is that the Javadocs were generated directly from the .java source code file and the .class class file for each class. There is actually a program called javadoc that reads both files and generates the HTML documentation. Specially formatted comments in the source code file are lifted directly from the comments and placed into the documentation.
Click here to see the Baseball projects Javadoc pages.
The pages in the link above were generated with the javadoc program. Compare the documentation pages with the actual source code and you will see the same text in both places. You should also notice how special keywords (like @param and @returns) in the comments get translated in the Javadocs.
For more information, read the Javadoc Reference Web page.
Use of equals() and hashCode() methods
Two important additions to the Name class are the equals() and hashCode() methods. These are methods inherited from Object and are used by other classes to interact with your object. By default, these methods use very simple algorithms that don't always behave the way you might want. If this is the case, then your new classes need to override these methods to behave correctly.
equals()
We talked about the equals() method for object types like String and Integer in Chapter 4. Now we revisit this method again, but this time from the "other side." When you create a class that contains data, you should override the equals() method so that it properly compares itself against the other object. Your new equals() method should return true if the two objects are effectively the same, and false otherwise.
One important thing to notice is that since an object can be compared to any other object, including objects that are a whole different class, it's important to test the object's class as well. Otherwise, you could literally be comparing apples and oranges. The operator to do this is the instaceof operator. It will return true if an object is an instance of a class (or one of its superclasses).
Make sure that you review the equals() method for both the Name and BattingAverage classes.
hashCode()
The hashCode() method, like the equals() method, is inherited by all classes from the Object class. The purpose of this method may not be immediately obvious, but it does have an important purpose. A hash code is a numeric representation of an object's data. The hash code is an int value that has some how been derived from the object's data fields. The number itself doesn't have to actually mean anything, but it should change when the data changes, and it should be reproducible (not random) for a given set of data.
The basic rule of thumb is that if two objects are "equa,l" as defined by their equals() method, then their hash code values should also be equal.
We'll show you how the hash code is used a little later in this exercise. For now, just look at the hashCode() method for the Name class (we didn't do the BattingAverage class) to see how we generated a hash code from the data fields.
2) Create a graphical interface where you type in a player's name, hits, and at bats, and it calculates their average. Save the data in the structure from Question 1.
Answer:
See the answer to Question 3, since it's the same program.
3) To your program from Question 2, add the ability to search for a player's name. It will then search through all records and display any matches that it finds.
Answer:
We could have stored the different player stats in a Vector, or several Vectors. You solution may have done that. You would then need to cycle through the Vector, looking for a matching name. While this is fine, we wanted to show you a slightly better way, using a new class: the Hashtable.
A Hashtable is similar to a Vector, in that it holds objects. However, unlike a Vector or an array, where the objects are referenced by an index value, the Hashtable references its objects using a key. A key is another object, that you define, that is also stored in the Hashtable.
When you add an item to the Hashtable, you add it with the key, as follows:
Hashtable ht = new Hashtable();
// Use the "put" method to add an item.
ht.put(myKey, myObj);
This example adds the myObj object under the key, myKey. To retrieve the object, you use the key, as follows:
Object o = ht.get(myKey);
What this does is link myKey and myObj together in the Hashtable. What we did in our Baseball applet is store the BattingAverage objects in a Hashtable using the Name objects as keys. Thus, we link together a player's name and batting average.
Click here for the Baseball.java source code.
Click here to run the Baseball applet.
The reason this works is that we correctly implemented the hashCode() method for the Name class (see above). All hash table keys must have a valid hashCode() method.
What makes the Hashtable useful is that it will do our searching for us. We give it a key (the player's name) and it returns to us the BattingAverage object for that name.
Again, the idea of having the object itself to the work goes back to the basic object-oriented design. If you had to step through a Vector and compare Name objects together, you would be breaking the object-oriented model. If you are doing a search then you should be using a search object. If you can't use an existing one (like the Hashtable), then you should write one yourself.
Two of the fundamental container objects in computer science are the stack and the queue. Both are used as a temporary holding area for things waiting to be processed.
However, each handles its items differently.
A stack is like a stack of books. You can push an item onto the stack, which would be like placing a book on top of the stack. You can also pop an item off of the stack, which would be like taking a book off the top of the stack. The key is that the most recently added item (the one on top) is first one to be removed. The term for this is LIFO, which stands for Last-In-First-Out.
A queue, on the other hand, is like a waiting line, where the first person in line is the next one to be processed. This is known as First-In-First-Out, or FIFO. When you add an item to a queue, it is added to the "back." Similarly, when you remove and item from a queue, it is removed from the "front."
4) Build stack and queue classes. The stack should have push and pop methods to add and remove items. Likewise, the queue should have add and remove methods. Both classes should return items in the correct LIFO or FIFO order. Make sure to hide all fields and methods that the user doesn't need to use.
Answer:
The stack and queue classes should have been pretty straightforward to create. You should have been able to use a Vector for both. In fact, the two classes were almost identical to each other, save for the add/push and remove/pop methods.
You might have noticed that there is an actual Stack class that comes with Java. To avoid confusion, we called our classes MyStack and MyQueue. For fun, you might want to compare your stack class to Java's Stack class. Make sure to read the Javadoc API description of java.util.Stack and compare it to yours.
Click here for the MyQueue.java source code.
Click here for the MyStack.java source code.
Click here for an applet that tests the MyStack and MyQueue classes (called TestStack).
Click here for the TestStack.java source code.
5) Convert the DogButton applet from Chapter 9 into a class that extends JButton. Thus, you could add a DogButton anywhere. Note, this problem is a little tricky since you'll have to use other methods to load your pictures and sound files than the ones in the Applet class. Read the Javadoc pages for the Image and ImageIcon classes for information.
Answer:
This was a tricky problem, and the main trick was to pass in the Applet's own "code base" to the new DogButton object. The button has no idea where the image and sound files are, so you need to tell it. Thus, you need to pass in some kind of information into the constructor so that the button can load the images.
We actually have two (count 'em, two) solutions for you. Both answers pass an argument into the DogButton constructor. The first solution passes in a reference to the applet itself. This is the easier answer, as it requires almost no changes from the original DogButton class from Chapter 9.
The second solution is a "better" solution, but also a little more complex. It also requires using some skills we haven't taught you yet. The second solution passes in the base URL to the new DogButton constructor. This is all the information that the class really needs to load its images and sounds.
To implement the "new and improved" DogButton class, both solutions required the following changes:
· Extend JButton as opposed to JApplet.
· Properly call the super() constructor.
· Move init() code to the DogButton constructor.
Hopefully this Exercise sent you to the Javadoc pages early and often!
The "Applet" Solution
The "applet" solution passes in the entire JApplet object into the DogButton object's constructor. So, the applet must pass a reference to itself to the button. How is this done? By using the "this" variable. "this" is always a reference to an object's own self. So, in this case (pardon the pun) "this" refers to the instance of the JApplet that is running at the time.
Once the DogButton has a reference to an applet, it can use the applet to get a URL to use as a code base. It can also use the applet's methods for loading images and audio clips.
Click here for the "applet" version of the new DogButton.java source code.
Click here for the "applet" version of the new DogButtonTest.java source code.
Click here for run the "applet" version of the new DogButtonTest applet.
The "URL" Solution
The "URL" solution is an alternate solution to this problem. While passing in an applet as an argument is certainly easy, it's also a little bit of an over-kill. It also requires that the process using the button be an applet in the first place. You couldn't use that DogButton class with a Java application because it wouldn't have a code base, or any of the applet's methods.
So, since all the button really needs is a URL, that's all we give it. We then make the following changes:
· Create a constructor that takes a URL as an argument.
· Use alternate constructors of Image and AudioClip classes to load images and sounds.
· Pass in the value of the applet's codeBase() to the constructor, instead of the entire applet.
One tricky part about this solution is that you now need to create URLs by hand, using the base URL passed into the constructor. The URL class is more than just a String, so we can't just concatenate the URL and filenames together. Combining them together is actually very simple, but it requires that you, the programmer, handle any Exceptions that are thrown. Exceptions are covered in Chapter 11. There's not much to them, but if you don't know what they are, or how to deal with them, then they can look confusing. Don't worry about them for now, just know that they exist.
Click here for the "URL" version of the new DogButton.java source code.
Click here for the "URL" version of the new DogButtonTest.java source code.
Click here for run the "URL" version of the new DogButtonTest applet.