More on Using HashMaps


Like a list, you will often want to iterate through the contents of a hash table. Your needs may include iterating through only the keys, through only the values or through both simultaneously.

In Lesson 6, you built a ReportCard class. You used ReportCard to store a map of grades to corresponding messages for printing on a report card. As a reminder, here is the source for ReportCard:

 package sis.report; import java.util.*; import sis.studentinfo.*; public class ReportCard {    static final String A_MESSAGE = "Excellent";    static final String B_MESSAGE = "Very good";    static final String C_MESSAGE = "Hmmm...";    static final String D_MESSAGE = "You're not trying";    static final String F_MESSAGE = "Loser";    private Map<Student.Grade, String> messages = null;    public String getMessage(Student.Grade grade) {       return getMessages().get(grade);    }    private Map<Student.Grade, String> getMessages() {       if (messages == null)          loadMessages();       return messages;    }    private void loadMessages() {       messages =          new EnumMap<Student.Grade, String>(Student.Grade.class);       messages.put(Student.Grade.A, A_MESSAGE);       messages.put(Student.Grade.B, B_MESSAGE);       messages.put(Student.Grade.C, C_MESSAGE);       messages.put(Student.Grade.D, D_MESSAGE);       messages.put(Student.Grade.F, F_MESSAGE);    } } 

The following three new tests for ReportCard demonstrate how to iterate through each of the three possibilities. (These are unnecessary tests, and act more like language tests.) Each of the three tests show different but valid testing approaches.

 package sis.report; import junit.framework.*; import sis.studentinfo.*; import java.util.*; public class ReportCardTest extends TestCase {    private ReportCard card;    protected void setUp() { card = new ReportCard();    }    public void testMessage() {       // remove declaration of card since it is declared in setUp       ...    }    public void testKeys() {       Set<Student.Grade> expectedGrades = new HashSet<Student.Grade>();       expectedGrades.add(Student.Grade.A);       expectedGrades.add(Student.Grade.B);       expectedGrades.add(Student.Grade.C);       expectedGrades.add(Student.Grade.D);       expectedGrades.add(Student.Grade.F);       Set<Student.Grade> grades = new HashSet<Student.Grade>();       for (Student.Grade grade: card.getMessages().keySet())          grades.add(grade);       assertEquals(expectedGrades, grades);    } } 

The first test, testKeys, provides one technique for ensuring that you iterate through all expected values in a collection. You first create a set and populating it with the objects (grades in this example) you expect to receive. You then create a secondary set and add to it each element that you iterate through. After iteration, you compare the two sets to ensure that they are equal.

Perhaps you remember sets from grade school. (Remember Venn diagrams?) If not, no big deal. A set is an unordered collection that contains unique elements. If you attempt to add a duplicate element to a set, the set rejects it. In Java, the java.util.Set interface declares the behavior of a set. The class java.util.HashSet is the workhorse implementation of the Set interface. The HashSet class compares objects using equals to determine if a candidate object is a duplicate.

Based on your understanding of how a hash table works, it should be clear that you cannot guarantee that its keys will appear in any specific order. It should also be clear that each key is unique. You cannot have two entries in a hash table with the same key. As such, when you send the message keySet to a HashMap object, it returns the keys to you as a set.

As shown in testKeys, you iterate a Set much as you iterate a List, using a for-each loop.

In contrast, the values in a hash table can be duplicates. For example, you might choose to print the same message ("get help") for a student with a D or an F. A HashMap thus cannot return the values as a Set. Instead, it returns the values as a java.util.Collection.

Collection is an interface implemented by both HashSet and ArrayList. The Collection interface declares basic collection functionality, including the abilities to add objects to a collection and obtain an iterator from a collection. The Collection interface does not imply any order to a collection, so it does not declare methods related to indexed access as does the List interface. Both java.util.Set and java.util.List interfaces extend the Collection interface.

 public void testValues() {    List<String> expectedMessages = new ArrayList<String>();    expectedMessages.add(ReportCard.A_MESSAGE);    expectedMessages.add(ReportCard.B_MESSAGE);    expectedMessages.add(ReportCard.C_MESSAGE);    expectedMessages.add(ReportCard.D_MESSAGE);    expectedMessages.add(ReportCard.F_MESSAGE);    Collection<String> messages = card.getMessages().values();    for (String message: messages)       assertTrue(expectedMessages.contains(message));    assertEquals(expectedMessages.size(), messages.size()); } 

The test, testValues, uses a slightly different technique. You again create a Set to represent expected values, then add to it each possible value. You obtain the hash table values by sending the message values to the HashMap object.

As you iterate through the values, you verify that the list of expected values contains each element. The contains method uses the equals method to determine if the collection includes an object. Finally, you must also test the size of the secondary set to ensure that it does not contain more elements than expected.

The third test, testEntries, shows how you can iterate through the keys and associated values (the maps) simultaneously. If you send the message entrySet to a HashMap, it returns a Set of key-value pairs. You can iterate through this set using a for-each loop. For each pass through the loop you get a Map.Entry reference that stores a key-value pair. You can retrieve the key and value from a Map.Entry by using the methods getKey and getValue. The Map.Entry is bound to the same key type (Student.Grade in the example) and value type (String) as the HashMap object.

 public void testEntries() {    Set<Entry> entries = new HashSet<Entry>();    for (Map.Entry<Student.Grade,String> entry:          card.getMessages().entrySet())       entries.add(          new Entry(entry.getKey(), entry.getValue()));    Set<Entry> expectedEntries = new HashSet<Entry>();    expectedEntries.add(       new Entry(Student.Grade.A, ReportCard.A_MESSAGE));    expectedEntries.add(       new Entry(Student.Grade.B, ReportCard.B_MESSAGE));    expectedEntries.add(       new Entry(Student.Grade.C, ReportCard.C_MESSAGE));    expectedEntries.add(       new Entry(Student.Grade.D, ReportCard.D_MESSAGE));    expectedEntries.add(       new Entry(Student.Grade.F, ReportCard.F_MESSAGE));    assertEquals(expectedEntries, entries); } 

You need to test that the for-each loop iterates over all the entries. You can create a secondary set of expected entries, like in the previous tests, and compare this expected set to the set populated by iteration. To the expected set you need to add the appropriate key-value entries, but unfortunately, Map.Entry is an interface. No concrete class to store a key-value pair is available to you.

Instead, you will create your own Entry class to store a key-value pair. The process of iteration will instantiate Entry objects and add them to the "actuals" set. Also, you will populate the expected set with Entry objects. You can then compare the two sets to ensure that they contain equal Entry objects.

 class Entry {    private Student.Grade grade;    private String message;    Entry(Student.Grade grade, String message) {       this.grade = grade;       this.message = message;    }    @Override    public boolean equals(Object object) {       if (object.getClass() != this.getClass())          return false;       Entry that = (Entry)object;       return          this.grade == that.grade &&          this.message.equals(that.message);    }    @Override    public int hashCode() {       final int hashMultiplier = 41;       int result = 7;       result = result * hashMultiplier + grade.hashCode();       result = result * hashMultiplier + message.hashCode();       return result;   } } 

The class Entry is a quick-and-dirty class used solely for testing. I therefore chose to write no tests for it. The Entry class contains an equals method and its companion method hashCode. Since this is a quick-and-dirty test class, I might have chosen to omit the hashCode method, but a successful test requires it. Try running the tests without hashCode, and make sure you understand why the test fails without it.



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net