CHAPTER 13

java.util Package and the Collection Framework

The java.util package has lots of classes and interfaces. These are mainly related to the collection framework. Apart from the collection framework, it has many utility classes and also the date and time related classes. In Java, time is normally known and used as a long value in milliseconds from a standard base time known as “The Epoch”. Epoch is the time 1st January, 1970 0:0:0.0 GMT, which would be the same as 1st January, 1970 5:30:0.0 IST. There are many classes which encapsulate the millisecond value and provide methods to convert and extract the values related to the day, month, year, hour, minutes, seconds, etc. There are two main classes which use this, namely java.util.Date and java.util.Calendar. These are discussed in Section 13.1. The collection framework provides the various classes and interfaces related to most of the data structures in Java. The collection framework has been discussed in Section 13.3.

13.1 Date, TimeZone, Calendar AND THE GregorianCalendar CLASSES

The Date class in java.util package represents a specific instance in time, with millisecond precision. Most of the methods and constructors have been deprecated in view of the Calendar class, which replaces most of the functionality of the Date class. The Calendar class is more flexible and is more suited for internationalization. Let us look at some of the constructors and methods from the Date class.

The Date class has a constructor with no arguments. This constructor creates an instance of Date which represents the instance of time when the Date object was allocated. The instance of time is the one which is available from the platform. The current time from the platform is also available by using the method currentTimeMillis(). Another constructor of the Date class allows us to create an instance of Date by specifying time in milliseconds since the epoch. The common constructor and methods from the Date class are given in Listing 13.1.

 

Listing 13.1. Constructors and common methods from Date class

    1 // constructors
 2     public Date()
 3     public Date(long date)
 4 // methods
 5     public long getTime()
 6     public void setTime(long time)
 7     public int compareTo(Date anotherDate)
 8     public boolean before(Date when)
 9     public boolean after(Date when)
 10    public String toString()
 11    public boolean equals(Object obj)

The absolute millisecond value from epoch can be converted to a date in a specific calendar. The conversion depends on the time zone value. The Calendar class is an abstract base class. An instance of Calendar class maintains the time in millisecond value as well as the calendar field values like day, month, year, hour, minutes, seconds and era. The synchronization between the millisecond time value and the calendar fields is done whenever it is required because of some method invocation like get(). When doing this conversion from milliseconds to the calendar field values, there are many fields which are affected by the value of the time zone. The Calendar class also has a setting for the time zone offset value.

We have a class called TimeZone in the java.util package, which can represent a time zone. The instance of TimeZone maintains the offset value. The offset value of a time zone may be dependent on date because of the daylight savings applicable in those time zones. The time zone instances have an ID. Listing 13.2 gives some of the common methods. There is always a default time zone which is used by JVM. Initially it is the same as that of the native system. This time zone instance is available using the static method getDefault().

 

Listing 13.2. TimeZone methods

    1     public static TimeZone getDefault()
 2     public static void setDefault(TimeZone zone)
 3     public String getID()
 4     public String getDisplayName()
 5     public int getOffset(long date)
 6     public int getRawOffset()
 7     public void setRawOffset(int offsetMillis)
 8     public static String[] getAvailableIDs()
 9     public static String[] getAvailableIDs(int rawoffset)
 10    public static TimeZone getTimeZone(String ID)
 11    public static TimeZone getTimeZone(String id)

The Calendar class has getTime() and setTime() methods which can be used to get and set the millisecond value in the calendar using a Date instance.

The GregorianCalendar is the concrete sub-class of Calendar for interpreting the millisecond value from epoch according to the gregorian calendar.

From Java 5 onwards, we have a class called Formatter which can be used for interpreting the long millisecond value as a date from a calendar assuming a time zone. The instance of TimeZone class represents the time zone relative to UTC.

Date formatting can be done using java.text.SimpleDateFormat class.

(a) create an instance of SimpleDateFormat using a formatting String, e.g. "dd-MMM-yyyy" (b) use format() method to convert Date to String and (c) use parse() method to convert String to Date.

e.g.

    1 DateFormat dateFormat = new DateFormat("dd-MMM-yyyy");
 2 Date d = new Date();
 3 String s = dateFormat.format(d);
 4
 5 String s = "25-Dec-2005";
 6 Date d = dateFormat.parse(s);
13.2 Arrays CLASS

The Arrays class contains lots of utility methods related to operations on all types of arrays. These are all static methods and no instance is required to be created in order to use these utility methods. It has methods called fill(), equals(), sort(), binarySearch(), toString(), etc. All these methods are overloaded to support operations on any kind of array.

The fill() method could be used to fill the entire or a segment of any array with the same value. In general the method is of the form

    1     public static void fill(xxx[] array, xxx value}
 2     public static void fill(xxx[] array, int fromIndex, int toIndex
           , xxx value}

where xxx could be any data type, e.g. if we have an integer array a and want to fill all its elements with a value of -1, then we can do it as given below:

    1     Arrays.fill(a, -1);

The equals() method returns a boolean value, it takes two parameters of the same type of array. It makes a comparision for equality of each and every element in the two array, and also that the size of the two arrays should be the same.

The sort() can be used to sort any type of array. The sort() method for the reference type can also take a Comparator as a parameter.

The binarySearch() can be used to search the index position of the element with a particular value. The binary search can be done only on sorted arrays.

13.3 Collection FRAMEWORK

The Collection Framework was introduced in Java from 1.2. Prior to Java 1.2 there were independent classes for the various implementations of the Data Structures. The Collection framework was introduced with the purpose of standardizing and having interoperability between various data structures. The framework relies on a standard set of interfaces which may be implemented in different ways by the actual data structure implementation classes. The interfaces also have methods to allow for interoperation between the various data structure implementation classes. Here the data structures are used to manage instances and not for managing primitive types. For primitive types we only have arrays. The primitives may be used in the methods of the collection framework from Java 5.0 onwards, since we now also have the boxing and unboxing conversion, which will convert any primitive value to its wrapper instance if it is passed as a parameter. Some of the important interfaces from the Collection Framework are given in Figure 13.1.

13.3.1 Collection Interface

The base interface is the Collection interface which represents any kind of collection of Objects. The methods in this interface define the kind of operations which may be implemented by the various data structure implementation classes. The operations which can be performed on a collection in general can be categorized into basic operations, array operations and bulk operations. Also, as part of the collection framework specifications. It is advised that all non-abstract implementations of the Collection interface would provide at least one constructor which takes another Collection as a parameter and would then be initialized to contain all the elements from the given collection.

Figure 13.1 Collection interface hierarchy

Figure 13.1 Collection interface hierarchy

Basic operations: The basic operations for the base Collection interface are given in Listing 13.3.

 

Listing 13.3. Basic operations on collection

    1     public boolean   add(Object o)
 2     public boolean   remove(Object o)
 3     public int       size()
 4     public void      clear()
 5     public boolean   contains(Object o)
 6     public boolean   isEmpty()
 7     public Iterator  iterator();

The add() method is used to add a new Object into the collection.

The remove() method can remove the specified Object from the collection, this method uses the equals() method to determine the object to be removed. It removes only one instance from the collection for which the equals() method returns true. If there is no such Object in the collection then this method would simply return false.

The size() method returns the number of elements currently in the collection.

The clear() method removes all the elements from the collection, so after call to this method, size() would be 0.

The contains() method would check if the specified Object exists in the collection. It would use the equals() method with every element, and if it returns true for any element, then this method would return true.

The isEmpty() method would return true if the size is 0.

The iterator() method returns an instance of the Iterator interface (some class which implements the Iterator interface). An instance of the Iterator always has an underlying collection. These methods are used for iterating over all the elements of the underlying collection. The methods of the Iterator interface are given in Listing 13.4.

 

Listing 13.4. Methods of Iterator

    1     public boolean hasNext()
 2     public  Object next()
 3     public void    remove()

We can iterate over the elements of the underlying collection of an Iterator by using the next() method. The hasNext() method returns true, if there are more elements remaining for iteration, and the remove() method would remove the instance from the collection which was last accessed using the next() method. These methods allow us to have the following kind of code to iterate over the elements of a collection. Let us assume that we have a collection which only contains the objects of the Account type.

    1 …
 2 Collection c = …;
 3 …
 4 Iterator iter = c.iterator();
 5 Account ac = null;
 6 while (iter.hasNext()) {
 7     …
 8     ac = (Account)iter.next();
 9     …
 10     if (…) {
 11        iter.remove();
 12     }
 13     …
 14 }

Array operations: The array operations for the base Collection interface are given in Listing 13.5.

 

Listing 13.5. Array operations on a collection

    1     public Object[] toArray()
 2     public Object[] toArray(Object[] o)

The first toArray() method would always return an instance of the Object[], whose size is the same as the size of the collection. The array returned by the second method differs from the one returned by the first, here the array is not an instance of Object[], but instead it is of the type which matches the instance passed as parameter. If we pass an array of Account as a parameter then the instance returned will also be an Account[], so we can cast it and assign to the Account[]. Also the array which is passed as the parameter is also updated with the elements from the collection.

Now there are three cases regarding the size of the array which is passed as the parameter and the size of the collection. The size of the array may be less than the size of the collection or the size of the array may be greater than the size of the collection, or the sizes of the array and collection are the same. In all cases the size of the array returned matches the size of the collection. In case the size of the array is less than the size of the collection, the method would update whatever elements can be accomodated from the collection in the array and allocate a new array whose size is the same as the size of the collection, which is updated and returned by the method. Example, if the size of the collection is 10 elements and the array passed is of size 5, then the array which is passed is updated to have any of the 5 elements from the collection and it also allocates a new array of size 10, updates it by filling all the elements from the collection and returns the newly allocated array. If the size of the array passed is of size 15, then the array which is passed is updated such that the first 10 elements are the elements in the collection. The next 5 values are set to null and it also allocates a new array of size 10, updates it by filling all the elements from the collection and returns the new allocated array. If the size of the array is 10 then it would be updated from the elements of the collection and no new array is allocated, it returns the same instance which was given as the parameter.

A common way of using the second toArray method is as given in the listing below:

    1 …
 2 Collection c = …
 3 …
 4 Account[] ac = (Account[])c.toArray(new Account[c.size()]);
 5 …

Here the Account[] ac would be the instance of the array created in the parameter using

    1     new Account[c.size()]

Bulk operations: The bulk operations for the base Collection interface are given in Listing 13.6. These methods allow us to have interoperability between various types of collections.

 

Listing 13.6. Bulk operations on Collection

    1     public boolean addAll(Collection c)
 2     public boolean removeAll(Collection c)
 3     public boolean retainAll(Collection c)
 4     public boolean containsAll(Collection c)

Let us say we have two references a and b which refer to two different Collection instances, then a.addAll(b) would result in a = a union b. a.removeAll(b) would result in a = a - b,

a.retainAll(b) would result in a = a intersection b, whereas a.containsAll(b) would check if b is a subset of a. An implementation of Collection is not very commonly used. It is the various subinter-faces of Collection whose concrete implementation classes are used. There is one abstract base class called AbstractCollection which implements the Collection interface.

13.3.2 Set and List Interfaces

What is a Set? In set theory, Set is a collection of unique elements; similarly here also the Set interface is used as an interface for implementations of those collections which ensure uniqueness of elements. A Set is a Collection which does not have duplicates, i.e. there cannot be any two elements a and b in a Set such that a.equals(b) would return true. The Set interface inherits all the methods from the Collection interface. It does not have any additional methods.

The commonly used concrete implementation class for the Set interface is the HashSet class. There is also a base abstract class implementing the Set interface. This is the AbstractSet class. The HashSet class extends from this abstract class.

A list is an ordered Collection where elements are maintained by index positions.

13.3.2.1 Basic Operations

The List interface extends the Collection interface. The basic operations inherited from the base Collection interface are given in Listing 13.3. The additional basic operations are given in Listing 13.7.

 

Listing 13.7. Additional basic methods for List

    1     public void         add(int index, Object o)
 2     public Object       remove(int index)
 3     public Object       get(int index)
 4     public Object       set(int index, Object o)
 5     public int          indexOf(Object o)
 6     public int          lastIndexOf(Object o)
 7     public List         subList(int start, int end)
 8     public ListIterator listIterator()
 9     public ListIterator listIterator(int index)

These methods involve the indexes. The add() method with index value inserts the Object at the given index position. So if a List has 10 elements their index positions are from 0–9. Now if we add a new object at index position 5, then elements from 5–9 will be shifted to 6–10 and the new object will be at index position 5. Similarly, elements can be removed using the index position using the remove() method with index. The remove() method returns the instance which has been removed from the list. The get() method can be used to obtain an object at the given index position. The set() method replaces an instance at the given index position in the List with the new instance, and returns the old instance which has been replaced.

The indexOf() and the lastIndexOf() methods search for an element and return the first found element’s index position in the List. The indexOf() method searches from left to right, whereas the lastIndexOf() method searches from right to left. If the element is not found in the List, then these methods return a -1. The subList() method creates a new List with elements between the specified start and end index positions within the List. The elements included in the new List are the elements from index positions start index upto but not including the element at the end index position in the current List. The last two methods return an instance of ListIterator, which allows us to iterate over all the elements in the List.

In case of a List the iteration can be done in both directions using the ListIterator. A ListIterator always has an underlying List and has methods for navigating in both directions. The ListIterator interface extends from the Iterator interface. The methods in the ListIterator are given in Listing 13.8.

 

Listing 13.8. Methods of ListIterator

    1     public boolean hasNext()
 2     public Object next()
 3     public int nextIndex()
 4
 5     public boolean hasPrevious()
 6     public Object previous()
 7     public int previousIndex()
 8
 9     public void     remove()
 10    public void     add(Object o)
 11    public void     set(Object o)

The first three methods are the methods in the forward direction. The ListIterator maintains a current position within the underlying List. This position is between two index positions, identified by the previous index and the next index. The nextIndex() and the previousIndex() methods return the previous and the next index values with respect to the current position. The next() method retrieves the instance which is currently at the next index position in the list, and the current position increases by one, i.e. the values of the next index and previous index will increase by one. Similarly the previous() method retrieves the instance which is at the previous index position, and decreases the current index by one. The hasNext() and hasPrevious() methods may be used before calling next() and the previous() methods to ensure that the next index is not outside the list range.

The last three methods remove(), add() and set() will update the underlying List. The remove() method removes the last accessed element. The element may have been accessed either by using the next() or the previous() method. The set() method replaces the last accessed element in the underlying List and the add() method inserts the new element at the current position, i.e. between the previous index and the next index in the underlying List.

13.3.2.2 Bulk Operations

The bulk operations given in Listing 13.3 are inherited from the Collection interface by the List interface. The additional bulk operations are given in Listing 13.9.

 

Listing 13.9. Additional bulk operations in List

    1     public void addAll(int index, Collection c)

The addAll() method with index value inserts elements from any collection into a list starting at the specified index position.

The commonly used concrete implementation classes for the List interface are the ArrayList and the Vector classes. Here the Vector class is a legacy class. It is part of the Java API, right from Java 1.0.

These two classes have been derived (inherited) from the AbstractList class. The AbstractList class is an abstract class implementing the List interface. This class has implementations of some of the methods from the interface, and if we want to create our own implementation of List interface, then this class is a good starting point.

13.3.2.3 Vector Class

The Vector class was modified from Java 1.2 to implement the List interface. Also it was made to extend from the AbstractList class rather than from the Object class, which was the case before Java 1.2. There are a lot of other methods in the Vector class which are the methods right from 1.0, which are doing the same activities as the methods of the List interface. The constructors and additional methods (methods not already in the List interface) for the Vector class are given in Listing 13.10.

 

Listing 13.10. Vector class constructors and methods

    1     public Vector()
 2     public Vector(int initialCapacity)
 3     public Vector(int initialCapacity, int capacityIncrement)
 4     public void addElement(Object obj)
 5     public void insertElementAt(Object obj, int index)
 6     public boolean removeElement(Object obj)
 7     public void removeElementAt(int index)
 8     public void removeAllElements()
 9     public void setElementAt(Object obj, int index)
 10    public void copyInto(Object[] anArray)
 11    public Object elementAt(int index)
 12    public Object firstElement()
 13    public Object lastElement()
 14    public int indexOf(Object obj, int start)
 15    public int lastIndexOf(Object obj, int start)
 16    public Enumeration elements()
 17    public int capacity()
 18    public void ensureCapacity(int minCapacity)
 19    public void trimToSize()
 20    public void setSize(int newSize)

Every Vector has a capacity and a capacityIncrement. The capacity of the Vector will be greater than or equal to the size of the Vector. The capacityIncrement is the value by which the capacity is increased if the capacity is required to be increased. If the capacityIncrement is set to 0, then the capacity is always doubled whenever it is required to be increased. The value of the capacityIncrement can be specified in the constructor only. If the value of the capacityIncrement is not specified then its default value is 0.

The addElement() method is equivalent to the add() method with one parameter of the List interface. The insertElementAt() method is equivalent to the add() method with two parameters in the List interface. Similarly the methods removeElement() and removeElementAt() are equivalent to the remove() methods in the List interface. The removeAll() method is equivalent to the clear() method in the List interface. The copyInto() method takes an Object[] as a parameter and will update it similar to the toArray() method. Note that here the method does not return any array, it only updates the array which is passed as a parameter.

The methods elementAt() and the setElementAt() are equivalent to the get() and set() methods in the List interface. The firstElement() and lastElement() are simply convenience methods to get the first and the last element from the Vector. The methods indexOf() and lastIndexOf() are overloaded to allow us to specify the start index for the search.

The capacity() returns the current capacity of the Vector. The method ensureCapacity() increases the capacity to at least the value specified if it is less than the specified value and the method trimToSize() reduces the capacity of the Vector to match the size. The setSize() method can change the size of the Vector. When the size is reduced then the last elements are lost and when the size is increased, then the elements at the increased indexes are nulls.

13.3.2.4 Enumeration Interface

To iterate over all the elements of a Vector, we have a method called elements(). This method returns an instance of the Enumeration interface. This interface is a legacy interface to iterate over various elements. This interface has only two methods as given in Listing 13.11. There are a lot of places in the API, where we come across the Enumeration interface.

 

Listing 13.11. Methods of Enumeration

    1     public boolean hasMoreElements()
 2     public Object nextElement()

EXERCISE 13.1 Update the Account class of Exercise 12.2 to using List to maintain all the Transaction instances instead of the array. Also update the printPassbook() method accordingly.

The updated Account class using List instead of the array earlier is given in Listing 13.12.

 

Listing 13.12. Account class using List to maintain Transactions

    1 package bank;
 2
 3 import java.util.Date;
 4 import java.util.List;
 5 import java.util.ArrayList;
 6
 7 public abstract class Account implements Comparable<Account> {
 8
 9     private int accountNumber;
 10     private String name;
 11 //    protected double balance;
 12     private double balance;
 13
 14 //    private Transaction[] passbook = new Transaction[100];
 15 //    private int nextIndexInPassbook = 0;
 16     private List passbook = new ArrayList();
 17
 18     public class Transaction {
 19         private Date dateOfTransaction = new Date();
 20         private String naration;
 21         private boolean credit;
 22         private double amount;
 23
 24         public Transaction(String n, boolean c, double amt) throws
                NegativeAmountException {
 25             if (amt < 0) {
 26                 throw new NegativeAmountException("Negative "+(c ?
                       "credit" : "debit"), amt, Account.this);
 27             }
 28             this.naration = n;
 29             this.credit = c;
 30             this.amount = amt;
 31             Account.this.balance += getNetAmount();
 32 //            Account.this.passbook[nextIndexInPassbook++] = this;
 33             passbook.add(this);
 34         }
 35
 36         public Date getDateOfTransaction() {
 37             return this.dateOfTransaction;
 38         }
 39
 40         public String getNaration() {
 41             return this.naration;
 42         }
 43
 44         public boolean isCredit() {
 45             return this.credit;
 46         }
 47
 48         public double getAmount() {
 49             return this.amount;
 50         }
 51
 52         public double getNetAmount() {
 53             return this.credit ? this.amount : -this.amount;
 54         }
 55
 56         public String toString() {
 57             return dateOfTransaction+","+naration+","+(credit ? ("
                    0.0,"+amount) : (amount+", 0.0"));
 58         }
 59    }
 60
 61    public void printPassbook() {
 62        double runningBalance = 0;
 63        System.out.println("Passbook of "+name+" Account # "+
               accountNumber);
 64 //       for (transactionIndex = 0; transactionIndex <
       nextTransactionIndexInPassbook; transactionIndex++) {
 65        for (Object o : passbook) {
 66 //           t = passbook[transactionIndex];
 67             Transaction t = (Transaction)o;
 68             runningBalance += t.getNetAmount();
 69             System.out.println(t+","+runningBalance);
 70        }
 71        System.out.println("End of Passbook");
 72    }
 73
 74    public Account(int acno, String n, double openBal) throws
           NegativeAmountException {
 75 /*
 76        if (openBal < 0) {
 77            throw new NegativeAmountException("Negative Opening
                  Balance", openBal, this);
 78        }
 79 */
 80        this.accountNumber = acno;
 81        this.name = n;
 82 //       this.balance = openBal;
 83        new Transaction("Opening Balance", true, openBal);
 84    }
 85
 86    public int getAccountNumber() {
 87        return this.accountNumber;
 88    }
 89
 90    public String getName() {
 91        return this.name;
 92    }
 93
 94    public double getBalance() {
 95        return this.balance;
 96    }
 97
 98    public void deposit(double amt) throws NegativeAmountException
           {
 99 /*
 100        if (amt < 0) {
 101           throw new NegativeAmountException("Negative Deposit",
                 amt, this);
 102        }
 103 */
 104 //       this.balance += amt;
 105        new Transaction("Deposit", true, amt);
 106    }
 107
 108    public boolean withdraw(double amt) throws
            NegativeAmountException {
 109 /*
 110        if (amt < 0) {
 111            throw new NegativeAmountException("Negative Withdrawal
                   ", amt, this);
 112        }
 113 */
 114        if (this.balance < amt) {
 115            return false;
 116        }
 117 //       this.balance -= amt;
 118        new Transaction("Withdrawal", false, amt);
 119        return true;
 120    }
 121
 122    public void display() {
 123 //      System.out.println("Account:"+this.accountNumber+","+this
        .name+","+this.balance);
 124        System.out.println(this);
 125    }
 126
 127    private static int lastAccountNumber = 1000;
 128
 129    public Account(String n, double openBal) throws
           NegativeAmountException {
 130        this(++lastAccountNumber, n, openBal);
 131    }
 132
 133    public String toString() {
 134        return this.getClass().getName()+":"+this.accountNumber+","
               +this.name+","+this.balance;
 135    }
 136
 137    public boolean equals(Object obj) {
 138        if (this.getClass() != obj.getClass()) {
 139            return false;
 140        }
 141        return this.accountNumber == ((Account)obj).accountNumber;
 142    }
 143
 144    public int hashCode() {
 145        return this.accountNumber;
 146    }
 147
 148    public int compareTo(Account ac) {
 149        return this.accountNumber - ac.accountNumber;
 150    }
 151 }

13.3.3 Map Interface

A Map maintains mapping of a key with a value. The key and value could be any type of object. In Map, a key is mapped to a single value only, i.e. the keys in a Map are unique. The various methods of the Map interface are given in Listing 13.13.

 

Listing 13.13. Methods in the Map interface

    1     public Object     put(Object key, Object value)
 2     public Object     get(Object key)
 3     public Object     remove(Object key)
 4     public int        size()
 5     public void       clear()
 6     public boolean    isEmpty()
 7     public boolean    containsKey(Object key)
 8     public boolean    containsValue(Object value)
 9     public Set        keySet()
 10    public Collection values()
 11    public Set        entrySet()
 12    public void       putAll(Map m)

The put() method normally creates a new key value pair and returns null. In case the key is already in use then the new value will be mapped to the key and it will return the old value which was previously mapped to that key. The get() method retrieves the value which was mapped to the key object using a put() method. In case the key was not mapped, then it would return a null. The remove() method removes the key value pair (mapping) for the specified key and returns the value which was mapped to the key. So all the three methods put(), get() and remove() return the value which was mapped to the key, but the put and remove method also update the Map. Similar to the Collection, here also we have the methods for size returning the number of key value pairs in the Map. The clear() method removes all the key value pairs, and isEmpty() checks if the size is 0. The containsKey() and containsValue() methods check if the specified object is used as a key (containsKey) or as a value (containsValue) within the Map. The containsKey() would be efficient in searching since the key value pairs are maintained using the key object, but the containsValue() method is not efficient. The keySet() method would return a Set containing all the keys used in the Map. Since the keys are unique, this method returns a Set. The values() method returns a Collection containing all the value objects which have been mapped to the keys within the Map. This can have duplicates since the same value may have been mapped to more than one key. The method entrySet() returns a Set containing elements of the type Map.Entry. The Map maintains the key value pairs as instances of Map.Entry. An instance of Map.Entry represents a single key value pair, the entrySet() method returns all the key value pairs as instances of Map.Entry. The Map.Entry is a nested interface in the Map interface. The methods of the Map.Entry interface are given in Listing 13.14.

 

Listing 13.14. Methods of Map.Entry

    1     public Object    getKey()
 2     public Object    getValue()
 3     public Object    setValue()
 4     public boolean   equals(Object o)
 5     public int       hashCode()

The getKey() and the getValue() methods return the key or the value objects in the key/value pair, and the setValue() method replaces the value object and returns the old value object, which is being replaced. The equals() method should be overridden, such that two Map.Entry are equal if their keys are equal. The hashCode() method needs to be overridden to be consistent with the equals() method.

The putAll() method is the only bulk operation available in the Map interface. Using putAll() method, all the key value pairs from the given Map are put into the current Map.

The commonly used concrete implementation classes for the Map interface are the HashMap and the Hashtable classes. Here the Hashtable class is a legacy class. It is part of the Java API, right from Java 1.0.

The class HashMap has been derived from the AbstractMap class. The AbstractMap class is an abstract class implementing the Map interface. This class has implementations of some of the methods from the interface, and if we want to create our own implementation of the Map interface, then this class is a good starting point. Note that the Hashtable has been modified from Java 1.2 to implement the Map interface, but this class does not extend from the AbstractMap class. It still extends from the abstract class Dictionary. This is done to have backward compatibility of some old code which may be using the Hashtable instances as Dictionary. Here it is important to note that the implementation of the Map interfaces use the hashCode() method to organize its key value pairs. So, if the implementation of the hashCode() and equals() are not consistent for the key objects, then the methods of Map may not give consistent results, i.e. given any two objects of the key, a and b. If a.equals(b) is true, then a.hashCode() must be equal to b.hashCode(). The consistency requirement for the hashCode() and equals() is discussed earlier in Section 8.1.1.

EXERCISE 13.2 Define a class called Bank, which would maintain all the Account instances of Exercise 13.1 by their account number using a Map, where the Integer account number is key and is mapped to the Account instance having the same account number. The Bank class may provide methods for opening a new account, depositing and withdrawing amount into an account, displaying and printing passbook for a given account identified by the account number and also for printing a list of all the accounts in the Bank.

One of the possible implementations of the Bank class is given in Listing 13.15 along with an exception class for an invalid account number in Listing 13.16.

 

Listing 13.15. Bank.java

    1 package bank;
 2
 3 import java.util.Map;
 4 import java.util.HashMap;
 5 import java.util.Collection;
 6
 7 public class Bank {
 8
 9     private String name;
 10     private int lastAccountNumber;
 11     private Map accountMap = new HashMap();
 12
 13     public Bank(String n, int bankCode) {
 14         this.name = n;
 15         this.lastAccountNumber = bankCode * 10000;
 16     }
 17
 18     public String getName() {
 19         return this.name;
 20     }
 21
 22     public int getBankCode() {
 23         return this.lastAccountNumber / 10000;
 24     }
 25
 26     public int openSavingsAccount(String n, double openBal) throws
            NegativeAmountException {
 27        Account ac = new SavingsAccount(++lastAccountNumber, n,
                openBal);
 28        accountMap.put(ac.getAccountNumber(), ac);
 29        return ac.getAccountNumber();
 30      }
 31
 32      public int openCurrentAccount(String n, double openBal) throws
             NegativeAmountException {
 33         Account ac = new CurrentAccount(++lastAccountNumber, n,
                openBal);
 34         accountMap.put(ac.getAccountNumber(), ac);
 35         return ac.getAccountNumber();
 36      }
 37
 38      private Account getAccount(int acno) throws
              NoSuchAccountExecption {
 39          Account ac = (Account)accountMap.get(acno);
 40          if (ac == null) {
 41              throw new NoSuchAccountException(acno);
 42          }
 43          return ac;
 44      }
 45
 46      public void deposit(int acno, double amt) throws
              NegativeAmountException, NoSuchAccountException {
 47          getAccount(acno).deposit(amt);
 48      }
 49
 50      public boolean withdraw(int acno, double amt) throws
              NegativeAmountException, NoSuchAccountException {
 51          return getAccount(acno).withdraw(amt);
 52      }
 53
 54      public void display(int acno) throws NoSuchAccountException {
 55          getAccount(acno).display();
 56      }
 57
 58      public void printPassbook(int acno) throws
             NoSuchAccountException {
 59          getAccount(acno).printPassbook();
 60      }
 61
 62      public void listAccounts() {
 63          Collection accounts = accountMap.values();
 64          System.out.println("Account list for bank: "+name);
 65          for (Object o : accounts) {
 66               System.out.println(o);
 67          }
 68          System.out.println("End of Account list");
 69      }
 70 }

 

Listing 13.16. NoSuchAccountException.java

    1 package bank;
 2
 3 public class NoSuchAccountException extends Exception {
 4
 5     private int accountNumber;
 6     NoSuchAccountException(int acno) {
 7         this.accountNumber = acno;
 8     }
 9     public int getAccountNumber() {
 10        return this.accountNumber;
 11     }
 12     public String toString() {
 13        return super.toString() + ":"+this.accountNumber;
 14     }
 15 }

13.3.3.1 Sorted Collections

There are two interfaces which maintain elements in a sorted order. These are the SortedSet and the SortedMap interfaces. There are concrete implementation classes for these two interfaces, namely the TreeSet and the TreeMap. The TreeSet maintains all the elements in a sorted order. The logic for ordering the elements is specified normally by using a Comparator in the constructor of the TreeSet interface. Here, when we use the Iterator to iterate over the elements of the SortedSet, then they will be in the order according to the Comparator. The working of the Comparator interface is discussed in Section 10.1.2 in Chapter 10. When we do not specify the Comparator in the constructor of the HashSet, then the elements added to the HashSet must be Comparable, otherwise the add method would throw a ClassCastException.

The TreeMap is a concrete implementation of the SortedMap interface. The TreeMap maintains the key value pairs in a sorted order, ordered by the key objects. The keys used in the TreeMap must be Comparable or a Comparator must be specified in the constructor of the TreeMap.

13.3.4 Generics in the Collection Framework

Generics is a feature introduced in the Java programming language from Java 5. Generics is about defining classes and interfaces with parameters. The Collection interface and its subinterfaces like the Set and List are all defined with parameters. They are all said to be of generic type. The interface Collection has been defined as given in Listing 13.17.

 

Listing 13.17. Collection with generics

    1 package java.util;
 2
 3 …
 4 public interface Collection<E> {
 5     public boolean add(E e)
 6     …
 7     public Iterator<E> iterator()
 8     …
 9 }

Line 4 in Listing 13.17 shows how an interface or a class can have a declaration of a parameter. The Collection interface has a type parameter called E. Whenever the Collection interface is used as a data type, a reference data type needs to be specified as the value for E. This is known as type invocation of the type Collection, e.g. the declarations of

    1     Collection<Account> ca;
 2     Collection<Transaction> ct;
 3     Collection<Rectangle> cr;

are different invocations of the Collection interface. There is only one interface called Collection. These invocations are not new interfaces, rather they are different invocations of the same interface Collection. The type parameter in the generic type is used only by the compiler and is not available at the runtime. This feature is referred to as type erasure at the runtime. Generics is implemented with type erasure at the runtime.

A type parameter in a generic type definition can be used as a data type within the definition, e.g. in Line 5 in Listing 13.17, add() has a parameter which is declared to be of type E, which is the type parameter. In Line 7, in the same listing, the type parameter is used in the return type. By using the type parameter as a parameter in a method, we simply ask the compiler to carry out a check on the type of expression used in the method. Let us consider similar methods available in the List interface.

    1 public interface List<E> extends Collection<E> {
 2     public void add(int index, E e)
 3     public E get(int index)
 4     public void set(int index, E e)
 5     public ListIterator<E> listIterator()
 6     …
 7 }

Here the methods like get() and the ListIterator() return type uses the type parameter. When the get() method has a return type which is the type parameter then it helps us in writing a code without using casts. Here we could assign the element directly to the usage of the type parameter without using a cast.

 

Listing 13.18. Sample code segment showing usage of generics with List

    1     List<Transaction> passbook = new ArrayList<Transaction>();
 2     Transaction t = new Transaction(…); // some invocation of
            constructor
 3     passbook.add(t);
 4     Transaction t = passbook.get(0);
 5     Iterator<Transaction> iter = passbook.iterator();
 6     for (Transaction transaction : passbook) {
 7         // processing for each transaction in the passbook
 8     }

In Listing 13.18, in Line 4, we have used the get() method on passbook and assigned its return value directly (without casting) to the Transaction type, which is the type parameter for the passbook.

There are restrictions on where and how the type parameters can be used within the class or interface which has a generic type parameter. The type parameter cannot be used in static context. The type parameter may be used in any non-static type declaration, method return type, a parameter type declaration, a local variable declaration or a nested type declaration. The type parameter cannot be used for creating instances of the type parameter or an array of the type parameter, i.e. we cannot use new to create instances of the type parameter or arrays of the type parameter, e.g. new T() or new T[10] is not valid, where T is a type parameter. The following listing shows the different places where the generic type parameter may be used within a class which has a type parameter.

    1 public class GenericClass<A, B> {
 2     private A a; // non-static type declaratino
 3     public A method() {…} // return type of a method
 4     private Collection<A> collectionOfA; // a parameter type
                 declaration
 5     public void method1() {
 6         A a = null; // local variable
 7     }
 8     public class NestedClass<A> {…} // nested class declaration
 9 }

13.3.4.1 Using Wild Cards with Type Parameter

When we declare a variable of a generic type, we need to specify the type to be substituted for the type parameter, e.g. List<Account> accounts; or List<SavingsAccount> savingsAccounts;. The variable accounts can refer to any instance which is created as a List of Account, e.g. accounts = new ArrayList<Account>(); is a valid assignment. Similarly savingsAccounts = new ArrayList<SavingsAccount>(); is also a valid assignment. Now, we also know that SavingsAccount is a sub-type of Account. So, is List<SavingsAccount> a sub-type of List<Account>? Is it possible for us to assign savingsAccounts to accounts? This kind of assignment may initially seem to be fine, but if we look at it carefully we find that now, it would be possible to add elements of any type of account into the same list which is referred by the savingsAccounts variable by invoking the add method on the accounts instance. So, even though we have Account being a super-type of SavingsAccount, List<Account> is not a super-type of List<SavingsAccount>.

When we declare a variable of a generic type, we can use a wild card for the type parameter to indicate that it can refer to any kind of base generic type, i.e. if we declare List<?> anyList; then anyList is a super-type of any kind of List. The wild card(?) character represents an unknown type, so anyList is a List of unknown. We can indicate bounds on the type parameter by using extends along with the wild card, e.g. List<? extends Account> anyAccounts; declares the variable anyAccounts to refer to any of List, where the type parameter is a sub-type of Account. The bound for a generic type can also be specified using the super keyword, in which case it would mean the type which has the same type as the bound or any of the super-type of the bound, e.g. a declaration of List<? super Number> nList; would mean that the variable nList would be able to refer to any List, whose type parameter is a Number or a super-type of Number. Figure 13.2 shows the super-type and sub-type relationships between generic types.

13.3.4.2 Generic Methods and Constructors

Just like we define classes and interfaces with type parameter, even a method or a constructor may also have a type parameter, which is local to that method or constructor. The type parameter has to be declared just before the return type of the method following the modifiers for the method. This type parameter can be used as return type or as parameter type or anywhere locally within the method in which it is declared in a manner similar to the type parameter for the class or interface, e.g. The method toArray() in the Collection interface, is declared as follows:

Figure 13.2 Super-type–sub-type relation between generic types

Figure 13.2 Super-type–sub-type relation between generic types

 

    1     public abstract <T> T[] toArray(T[] array);

Let us now look at the methods in the collection framework which have been affected by generics.

13.3.4.3 Collection Framework Revised for Generics

Let us look at the changes in the Collection, List and the Map interfaces due to generics.

The idea here is that if we have a collection of a particular type, then type checking should be done for the elements being added or replaced in the collection. So, type checking is only required in the methods which would add and replace elements in the collection, and not in the methods which remove elements. Listing 13.19 gives the methods which have been affected by generics; other methods are the same as in Listings 13.3, 13.5 and 13.6.

 

Listing 13.19. Collection methods revised for generics

    1 public interface Collection<E> extends Iterable<E> {
 2     public boolean add(E e);
 3     public Iterator<E> iterator();
 4     public <T> T[] toArray(T[] array);
 5     public boolean addAll(Collection<? extends E> c);
 6     public boolean removeAll(Collection<?> c);
 7     public boolean retainAll(Collection<?> c);
 8     public boolean containsAll(Collection<?> c);
 9 }

In the Collection interface the generic type checking is done in case of the add() method. In the iterator() method the return type is the iterator of the same type as the type of Collection. The revised methods of the Iterator interface are given in Listing 13.20.

 

Listing 13.20. Iterator methods revised for generics

    1 public interface Iterator<E> {
 2     public E next();
 3     public boolena hasNext();
 4     public void remove();
 5 }

The toArray() method has a local type parameter, which declares the return type as the array of the same type which is passed as parameter. The addAll() method allows addition of elements from any collection, whose type parameter is the same or a sub-type of the type parameter of the current collection. In case of methods removeAll(), retainAll() and containsAll() there is no restriction on which type of collection is used as a parameter.

Similarly Listing 13.21 lists the methods in the List interface.

 

Listing 13.21. List methods revised for generics

    1 public interface List<E> extends Collection<E> {
 2     public void add(int index, E e);
 3     public E remove(int index);
 4     public E get(int index);
 5     public E set(int index, E e);
 6     public List<E> subList(int fromIndex, int toIndex);
 7     public ListIterator<E> listIterator();
 8     public ListIterator<E> listIterator(int index);
 9     public boolean addAll(int index, Collection<? extends E> c);
 10 }

Listing 13.22 shows the methods of the Map interface, revised for generics.

 

Listing 13.22. Map methods revised for generics

    1 public interface Map<K, V> {
 2     public V put(K key, V value);
 3     public V get(Object o);
 4     public V remove(Object 0);
 5     public Set<K> keySet();
 6     public Collection<V> values();
 7     public Set<Map.Entry<K, V>> entrySet();
 8     public void putAll(Map<? extends K,? extends V> map);
 9 }

The revised Map.Entry interface is given in Listing 13.23.

 

Listing 13.23. Map.Entry methods revised for generics

    1 public interface Map.Entry<K, V> {
 2     public K getKey();
 3     public V getValue();
 4     public V setValue(V value);
 5 }

13.3.4.4 Iterable Interface and for-each Loop

From Java 5, an interface called Iterable has been introduced. The Collection interface implements the Iterable interface. This interface has only one method called iterator(). This Iterable interface is also a generic type and has a type parameter. The iterator() method returns Iterator of the type parameter of the Collection. So, in Listing 13.18, in Line 5, we have assigned the return value of iterator() to Iterator<Transaction>. We know that from Java 5 onwards, we can use a for-each loop to iterate over elements of an array. This same for-each loop can also be used to iterate over elements of Iterable, as shown in Lines 6–8 in Listing 13.18.

EXERCISE 13.3 Update the Account class from Exercise 13.1 and the Bank class from Exercise 13.2 to use generics when using the List and Map.

The Account class and the Bank class updated for generics are given in Listings 13.24 and 13.25.

 

Listing 13.24. Account.java

    1 package bank;
 2
 3 import java.util.Date;
 4 import java.util.List;
 5 import java.util.ArrayList;
 6
 7 public abstract class Account implements Comparable<Account> {
 8
 9     private int accountNumber;
 10    private String name;
 11    private double balance;
 12
 13    private List<Transaction> passbook = new ArrayList<Transaction>();
 14
 15    public class Transaction {
 16        private Date dateOfTransaction = new Date();
 17        private String naration;
 18        private boolean credit;
 19        private double amount;
 20
 21        public Transaction(String n, boolean c, double amt) throws
               NegativeAmountException {
 22            if (amt < 0) {
 23               throw new NegativeAmountException("Negative "+(c ?
                    "credit" : "debit"), amt, Account.this);
 24            }
 25            this.naration = n;
 26            this.credit = c;
 27            this.amount = amt;
 28            Account.this.balance += getNetAmount();
 29            passbook.add(this);
 30        }
 31
 32        public Date getDateOfTransaction() {
 33            return this.dateOfTransaction;
 34        }
 35
 36        public String getNaration() {
 37            return this.naration;
 38        }
 39
 40        public boolean isCredit() {
 41            return this.credit;
 42        }
 43
 44        public double getAmount() {
 45            return this.amount;
 46        }
 47
 48        public double getNetAmount() {
 49            return this.credit ? this.amount : -this.amount;
 50        }
 51
 52        public String toString() {
 53            return dateOfTransaction+","+naration+","+(credit ? ("
                    0.0,"+amount) : (amount+", 0.0"));
 54        }
 55    }
 56
 57    public void printPassbook() {
 58        double runningBalance = 0;
 59        System.out.println("Passbook of "+name+" Account #
                "+accountNumber);
 60        for (Transaction t : passbook) {
 61             runningBalance += t.getNetAmount();
 62             System.out.println(t+","+runningBalance);
 63        }
 64        System.out.println("End of Passbook");
 65    }
 66
 67    public Account(int acno, String n, double openBal) throws
           NegativeAmountException {
 68        this.accountNumber = acno;
 69        this.name = n;
 70        new Transaction("Opening Balance", true, openBal);
 71    }
 72
 73    public int getAccountNumber() {
 74        return this.accountNumber;
 75    }
 76
 77    public String getName() {
 78        return this.name;
 79    }
 80
 81    public double getBalance() {
 82        return this.balance;
 83    }
 84
 85    public void deposit(double amt) throws NegativeAmountException
          {
 86        new Transaction("Deposit", true, amt);
 87    }
 88
 89    public boolean withdraw(double amt) throws
           NegativeAmountException {
 90        if (this.balance < amt) {
 91            return false;
 92        }
 93        new Transaction("Withdrawal", false, amt);
 94        return true;
 95    }
 96
 97    public void display() {
 98        System.out.println(this);
 99    }
 100
 101    private static int lastAccountNumber = 1000;
 102
 103    public Account(String n, double openBal) throws
             NegativeAmountException {
 104        this(++lastAccountNumber, n, openBal);
 105    }
 106
 107    public String toString() {
 108        return this.getClass().getName()+":"+this.accountNumber+","
                +this.name+","+this.balance;
 109    }
 110
 111    public boolean equals(Object obj) {
 112        if (this.getClass() != obj.getClass()) {
 113            return false;
 114        }
 115        return this.accountNumber == ((Account)obj).accountNumber;
 116    }
 117
 118    public int hashCode() {
 119        return this.accountNumber;
 120    }
 121
 122    public int compareTo(Account ac) {
 123        return this.accountNumber - ac.accountNumber;
 124    }
 125 }

 

Listing 13.25. Bank.java

    1 package bank;
 2
 3 import java.util.Map;
 4 import java.util.HashMap;
 5 import java.util.Collection;
 6
 7 public class Bank {
 8
 9     private String name;
 10    private int lastAccountNumber;
 11    private Map<Integer, Account> accountMap = new HashMap<Integer,
           Account>();
 12
 13    public Bank(String n, int bankCode) {
 14        this.name = n;
 15        this.lastAccountNumber = bankCode * 10000;
 16    }
 17
 18    public String getName() {
 19        return this.name;
 20    }
 21
 22    public int getBankCode() {
 23        return this.lastAccountNumber / 10000;
 24    }
 25
 26    public int openSavingsAccount(String n, double openBal) throws
           NegativeAmountException {
 27        Account ac = new SavingsAccount(++lastAccountNumber, n,
               openBal);
 28        accountMap.put(ac.getAccountNumber(), ac);
 29        return ac.getAccountNumber();
 30    }
 31
 32    public int openCurrentAccount(String n, double openBal) throws
           NegativeAmountException {
 33        Account ac = new CurrentAccount(++lastAccountNumber, n,
               openBal);
 34        accountMap.put(ac.getAccountNumber(), ac);
 35        return ac.getAccountNumber();
 36    }
 37
 38    private Account getAccount(int acno) throws
           NoSuchAccountException {
 39 //        Account ac = (Account)accountMap.get(acno);
 40        Account ac = accountMap.get(acno);
 41        if (ac == null) {
 42            throw new NoSuchAccountException(acno);
 43        }
 44        return ac;
 45    }
 46
 47    public void deposit(int acno, double amt) throws
           NegativeAmountException, NoSuchAccountException {
 48        getAccount(acno).deposit(amt);
 49    }
 50
 51    public boolean withdraw(int acno, double amt) throws
           NegativeAmountException, NoSuchAccountException {
 52        return getAccount(acno).withdraw(amt);
 53    }
 54
 55    public void display(int acno) throws NoSuchAccountException {
 56        getAccount(acno).display();
 57    }
 58
 59    public void printPassbook(int acno) throws
           NoSuchAccountException {
 60        getAccount(acno).printPassbook();
 61    }
 62
 63    public void listAccounts() {
 64        Collection accounts = accountMap.values();
 65        System.out.println("Account list for bank: "+name);
 66 //        for (Object o : accounts) {
 67        for (Account ac : accounts) {
 68             System.out.println(ac);
 69        }
 70        System.out.println("End of Account list");
 71    }
 72 }
13.4 Collections CLASS

Just like we have the Arrays class which has all utility methods relating to array handling, the Collections class contains lots of utility methods related to operations on all types of elements in the collection framework. These are all static methods and no instance is required to be created in order to use these utility methods. The commonly used methods from the Collections class are given Listing 13.26.

 

Listing 13.26. Common methods from Collections class

    1     public static <T> Enumeration<T> enumeration(Collection<T> c)
 2     public static <T> void fill(List<? super T> list, T obj)
 3     public static int frequency(Collection<?> c, Object o)
 4     public static <T> ArrayList<T> list(Enumeration<T> e)
 5     public static <T> T max(Collection<? extends T> coll,
           Comparator<? super T> comp)
 6     public static <T> T min(Collection<? extends T> coll,
           Comparator<? super T> comp)
 7     public static <T> List<T> nCopies(int n, T o)
 8     public static void reverse(List<?> list)
 9     public static void rotate(List<?> list, int distance)
 10     public static void shuffle(List<?> list)
 11     public static <? extends Comparable<? super T>> void sort(List<
            T> list)
 12     public static <T> void sort(List<T> list, Comparator<? super T>
            c)
 13    public static <T> Collection<T> unmodifiableCollection(
            Collection<? extends T> c)
 14     public static <T> List<T> unmodifiableList(List<? extends T> c)
 15    public static <T> Set<T> unmodifiableSet(Set<? extends T> c)
 16    public static <T> SortedSet<T> unmodifiableSortedSet(SortedSet
            <? extends T> c)
 17     public static <K, V> Map<K, V> unmodifiableMap(Map<? extends K,?
           extends V> m)
 18     public static <K, V> SortedMap<K, V> unmodifiableSortedMap(
           SortedMap<? extends K,? extends V> m)
13.5 StringTokenizer CLASS

The StringTokenizer class implements the Enumeration interface. It is used for iterating over various tokens within a string identified using a set of delimiting characters. These tokens are available as a String. The various constructors for the StringTokenizer are given in Listing 13.27.

 

Listing 13.27. Constructors for StringTokenizer

    1     public StringTokenizer(String s)
 2     public StringTokenizer(String s, String delimiters)
 3     public StringTokenizer(String s, String delimiters, boolean
           tokensasdelimiters)

In the first constructor the delimiters are not specified, so initially the default delimiters are considered to be all the white space characters. The second and third constructors have a parameter, where the set of delimiting characters can be specified. In the last constructor, the boolean parameter is used to indicate whether the delimiters are to be skipped or to be available as a separate token for the delimiters. In case it is set to true, then every alternate token will be the String containing the delimiting characters, which terminated the earlier token. By default, the delimiting characters are skipped and are not provided as a separate token. For e.g. the code given in listing below:

    1     String s = "tomcat:x:91:91:Apache Tomcat:/usr/share/tomcat5:/bin/sh";
 2     StringTokenizer st = new StringTokenizer(s, ",: 
	");
 3     while (st.hasMoreTokens()) {
 4         System.out.println(st.nextToken());
 5     }
 6     System.out.println("output for Delimiters as tokens follows:");
 7     StringTokenizer st = new StringTokenizer(s, ",: 
	", true);
 8     while (st.hasMoreTokens()) {
 9         System.out.println(st.nextToken());
 10    }

would print

tomcat

x

91

91

Apache

Tomcat

/usr/share/tomcat5

/bin/sh

output for Delimiters as tokens follows:

tomcat

:

x

:

91

:

91

:

Apache

:

Tomcat

:

/usr/share/tomcat5

:

/bin/sh

The common methods from the StringTokenizer class are given in Listing 13.28.

 

Listing 13.28. Methods for StringTokenizer

    1     public String nextToken()
 2     public boolean hasMoreTokens()
 3     public int     countTokens()
 4     public String  nextToken(String delimiters)

Internally a StringTokenizer maintains the set of delimiting characters and current position within the String. The nextToken() is used to get the next token from the current position. It would search for any of the characters specified in the delimiter string and create a string with characters from the current position till the first position from the current position, where any of the characters from the delimiter string are available. This method is overloaded to accept the delimiter string as a parameter. When the delimiter string is given, then the delimiters for the StringTokenizer instance are first updated and then it looks for the next token. For e.g. the code given below:

    1     String s = "bank.SavingsAccount:110110001, your name, 120345.0"
 2     StringTokenizer st = new StringTokenizer(s,":");
 3     String firstToken = st.nextToken();
 4     System.out.println(firstToken);
 5     String nextToken = st.nextToken(",");
 6     System.out.println(nextToken();
 7     while (st.hasMoreElements()) {
 8         System.out.println(st.nextToken());
 9     }

would be printed as follows:

bank.SavingsAccount

:110110001

your name

120345.0

The countTokens() method returns the number tokens still available from this StringTokenizer using the current setting of the delimiters. This value would decrease as we iterate to obtain next tokens. The hasMoreTokens() is used to determine whether we have reached the end of the string in which we are looking for tokens. The implementations of nextElement() and hasMoreElements() are similar to nextToken() and hasMoreTokens() except for the return type being String in case of nextToken() and Object in case of nextElement().

13.6 REGULAR EXPRESSIONS, Pattern AND Matcher CLASSES

The full coverage of regular expressions is beyond the scope of this book, and here we are just giving an introduction which can be helpful in getting started with regular expressions. Regular expressions are a kind of template to which several possible strings may match. The regular expression is a way of specifying such a template or a string pattern. A given string could be identified to be following the pattern specified by a regular expression. The pattern of character is specified using special characters, which have their own specific meanings, e.g. the ‘.’ character is used to mean any character. The regular expression x.y would mean any string which starts with the letter x followed by any character and then followed by the letter y. The ‘[’ and ‘]’ characters are used as enclosure to specify a possible set of character values, which could be allowed. So, a regular expression x[a-h]y would mean any string which starts with the letter x followed by any one of the letters between a to h, followed by the letter y. Similarly, the characters ‘*’ and ‘+’ are used as quantifiers for the preceding element. The ‘*’ character is used to denote zero or more occurrences of the preceding element and the ‘+’ character is used to denote one or more occurences of the preceding element, e.g. x[a-h]*y would mean any string which starts with x may be followed by zero or more occurrences of the letters between a and h and them immediately followed by a y.

Let us say we have an application which accepts command from the user and would need to identify which is the command given by the user and whether it follows the syntax for the command or not. We may define the syntax for the possible commands as follows:

open <type> <name> <open-bal>

  where the <type> is either S for savings or C for current

<name> is the name of the user and

<open-bal> may be specified using either whole number or a

decimal value with upto two digits only

deposit <acno> <amount>

  where <acno> is the numeric value of account number

  <amount> is the value of amount to be deposited using upto

      2 decimal places.

withdraw <acno> <amount>

  where <acno> is the numeric value of account number

  <amount> is the value of amount to be withdrawn using upto

       2 decimal places.

display <acno>

  where <acno> is the numeric value of account number

passbook <acno>

  where <acno> is the numeric value of account number

close <acno>

  where <acno> is the numeric value of account number

list

  used to list all the accounts

quit

  used to quit from the application

The application would like to match and check whether the input command matches the syntax of any of the possible commands and which specific command it matches. For this we may like to use the regular expression to represent the syntax. A regular expression of

([Oo][Pp][Ee][Nn])s+([SsCc])s+(w+)s+(d+(.d1,2))

could be used to match the syntax for the first command given above. Let us try to understand the regular expression by breaking it into smaller parts. In a regular expression the ( and ) are used to specify capturing groups. The initial ([Oo][Pp][Ee][Nn]) is used to match the word open in either case. The following s+ is used to match one or more white spaces. The following ([SsCc]) is used to capture the type as one of S or C in either case. The following s+ is again used to match one or more white spaces. The next group (w+) would capture the next word. The following s+ is again used to match one or more white spaces. The next group (d+(.d(1,2))) is used to capture a sequence of digits followed by at the most one group containing a . followed by one or two digits.

EXERCISE 13.4 Write a regular expression to specify the syntax for the other possible commands given earlier.

The possible regular expressions which could be used are given below:

([Dd][Ee][Pp][Oo][Ss][Ii][Tt])s+(d+)s+(d+(.d1,2))

for

deposit <acno> <amount>

  where <acno> is the numeric value of account number

  <amount> is the value of amount to be deposited using upto

      2 decimal places.

([Ww][Ii][Tt][Hh][Dd][Rr][Aa][Ww])s+(d+)s+(d+(.d1,2))

for

withdraw <acno> <amount>

  where <acno> is the numeric value of account number

  <amount> is the value of amount to be withdrawn using upto

      2 decimal places.

([Dd][Ii][Ss][Pp][Ll][Aa][Yy])s+(d+)

for

display <acno>

  where <acno> is the numeric value of account number

([Pp][Aa][Ss][Ss][Bb][Oo][Oo][Kk])s+(d+)

for

passbook <acno>

  where <acno> is the numeric value of account number

([Cc][Ll][Oo][Ss][Ee])s+(d+)

for

close <acno>

  where <acno> is the numeric value of account number

([Ll][Ii][Ss][Tt])

for

list

([Qq][Uu][Ii][Tt])

for

quit

13.6.1 Pattern and Matcher Classes

The Pattern and the Matcher classes belong to the java.util.regex package. The Pattern class represents the compiled form of a regular expression. Instances of a Pattern class are immutable. An instance of the Pattern class can be created by using the static method compile() of the Pattern class. The regular expression represented by an instance of Pattern may be then matched with an input string using the instance of Matcher. The instance of Matcher uses a Pattern instance and an input character sequence with which the pattern is to be matched. The instance of Matcher can be created by invoking the matcher() method on a Pattern instance.

The common methods of a Pattern class are given in Listing 13.29.

 

Listing 13.29. Methods of Pattern class

    1     public static Pattern compile(String regex)
 2     public static Pattern compile(String regex, int flags)
 3     public String pattern()
 4     public Matcher matcher(CharSequence input)
 5     public int flags()
 6     public static boolean matches(String regex, CharSequnce input)

The static method matches() is given as a convenience method where it matches the given regular expression with an input character sequence and returns whether the input sequence matches the specified regular expression. This method in turn compiles the regular expression to the Pattern instance and then uses the Matcher instance’s matches() method. The matches() method in the Pattern may be used sparingly. Where a particular regular expression is used multiple times, it is always advisable to create the Pattern instance and reuse it.

The Matcher instance is an engine for locating patterns in an input character sequence. The commonly used methods of the Matcher are given in Listing 13.30.

 

Listing 13.30. Methods of Matcher class

    1     public Pattern pattern()
 2     public Matcher usePattern(Pattern newPattern)
 3     public Matcher reset()
 4     public Matcher reset(CharSequence input)
 5     public boolean matches()
 6     public boolean lookingAt()
 7     public boolean find()
 8     public boolean find(int start)
 9     public String group()
 10    public int start()
 11    public int end()
 12    public String group(int group)
 13    public int start(int group)
 14    public int end(int group)

The pattern() returns the current pattern instance being used by the Matcher. The usePattern() method is used to change the Pattern object currently being used by the Matcher instance. Any group-related information about the previous match on the input character sequence will be lost but maintains the current position in the input character sequence. The reset() method without any input string resets the current position to 0 and the region is reset to the entire input sequence. Any previous match result will not be available from the group-related methods. The reset() method with input as parameter resets the current position as well as the input character sequence on which pattern matching or find would be carried out.

The methods matches(), lookingAt() and find() would use the Pattern instance in the Matcher and return a boolean value. The matches() would return true only if the entire input character sequence matches the pattern. The loookingAt() would return true only if the input character sequence is prefixed by the specified pattern, and the find() would return true only if the pattern occurs anywhere within the input character sequence. In all the cases the matching part of the string is identified as a group and the start and end indexes in the input character sequence are updated. These values are available using the group(), start() and the end() methods. The capturing groups within the regular expression are available using the group number of the capturing group in the group() method. Its corresponding start and the end index values within the input character sequence are also available using the group number in the start() and the end() methods.

13.7 Scanner CLASS

A Scanner breaks an input text character into tokens based on delimiters, and then converts them into primitive values or String. A Scanner instance is created by specifying some input source. This input source may be a fixed String or some source of bytes or characters. The sources of bytes and characters are normally instances of InputStream and Readable. The InputStream and Readable are discussed in Chapter 14. At this stage, we may simply use an instance of InputStream, System.in, which corresponds to the standard input of the JVM and is available as a static member of the System class. We very commonly create instances of Scanner as given below:

    1 Scanner stdScanner = new Scanner(System.in};

This instance of Scanner reads the input source of bytes from the standard input, converts them into sequence of character according to the default characterset of the native system and then breaks this character sequence into tokens using the delimiter, which by default is the white space characters. The common constructors for the Scanner class are given in Listing 13.31.

 

Listing 13.31. Constructors of Scanner

    1     public Scanner(InputStream source}
 2     public Scanner(InputStream source, String charsetname)
 3     public Scanner(File source)
 4     public Scanner(File source, String charsetname)
 5     public Scanner(Readable source)
 6     public Scanner(String source)

The constructors which have byte sources are overloaded to accept another parameter for the charsetname to allow the conversion of the bytes to characters according to the charsetname specified instead of converting from the native characterset.

The Scanner class maintains a delimiter, which can be specified as a regular expression in the form of a String or an instance of the Pattern class. The Scanner class has lots of nextxxx() methods. These methods would identify the next token based on the delimiter value from the source characters and then convert these into the appropriate primitive or string according to the method used. When converting to the integral primitive values, the radix value by default is 10, which can be set to any value between 2 and 36. Some of the common methods of the Scanner class are given in Listing 13.32.

 

Listing 13.32. Methods of Scanner class

    1     public Pattern delimiter()
 2     public Scanner useDelimiter(Pattern pattern)
 3     public Scanner useDelimiter(String pattern)
 4     public int radix()
 5     public Scanner useRadix(int radix)
 6     public byte nextByte()
 7     public byte nextByte(int radix)
 8     public short nextShort()
 9     public short nextShort(int radix)
 10    public int nextInt()
 11    public int nextInt(int radix)
 12    public long nextLong()
 13    public long nextLong(int radix)
 14    public float nextFloat()
 15    public double nextDouble()
 16    public boolean nextBoolean()
 17    public String nextLine()
 18    public String next()
 19    public String next(Pattern pattern)
 20    public String next(String pattern)
 21    public BigInteger nextBigInteger()
 22    public BigInteger nextBigInteger(int radix)
 23    public BigDecimal nextBigDecimal()

The delimiter used by a Scanner can be got and set by using the delimiter() and the useDelimiter() methods. The pattern() method returns the Pattern instance which is currently used for identifying end of token and the useDelimiter() method can take the pattern to be used as delimiter either as an instance of the Pattern class or as a String. The radix() method returns the default radix which would be used while returning the numeric values for the byte, short, int, long and the BigInteger. This default value of radix is initially 10, and can be set to any value between 2 and 36 using the useRadix() method. The next() method without any parameter simply returns the next token identified using the delimiter, whereas the next() method with pattern parameter would return the next token as an instance of String, only if it matches the specified pattern. The pattern to be matched by the token can be specified as an instance of Pattern or String. The nextLine() method ignores the current delimiter setting and returns the next token identified only by using the carriage return or new line character as delimiter. The other nextXXX() methods identify the token identified using the delimiter and then parses them to the types according to the method and the radix value.

EXERCISE 13.5 Define a class called BankServer which would use an instance of Scanner to take commands from the user and would identify the command specified by the user. It should then invoke an appropriate method on the instance of Bank as defined for Exercise 13.3. The Bank instance may be initialized in the constructor. The class can provide a method called service(), which executes a loop where it accepts a command and gives an appropriate response to the command till the quit command is entered by the user.

A possible implementation for the BankServer class is given in Listing 13.33.

 

Listing 13.33. BankServer.java

    1 package bank;
 2
 3 import java.util.*;
 4 import java.util.regex.*;
 5
 6 public class BankServer {
 7     private Bank bank;
 8     private Scanner commandScanner;
 9     private static Matcher commandMatcher = Command.QUIT.getSyntax
           ().matcher("something");
 10
 11    public enum Command {
 12        OPEN("([Oo][Pp][Ee][Nn])\s+([SsCc])\s+(\w+)\s+(\d
                +(\.(\d)1,2)?)"),
 13        DEPOSIT("([Dd][Ee][Pp][Oo][Ss][Ii][Tt])\s+(\d+)\s+(\d
                +(\.(\d)1,2)?)"),
 14        WITHDRAW("([Ww][Ii][Tt][Hh][Dd][Rr][Aa][Ww])\s+(\d+)\s
                +(\d+(\.(\d)1,2)?)"),
 15        DISPLAY("([Dd][Ii][Ss][Pp][Ll][Aa][Yy])\s+(\d+)"),
 16        PASSBOOK("([Pp][Aa][Ss][Ss][Bb][Oo][Oo][Kk])\s+(\d+)"),
 17        LIST("([Ll][Ii][Ss][Tt])"),
 18        CLOSE("([Cc][Ll][Oo][Ss][Ee])\s+(\d+)"),
 19        QUIT("[Qq][Uu][Ii][Tt]"),
 20        ;
 21        private Pattern syntax;
 22        private Command(String pattern) {
 23            this.syntax = Pattern.compile(pattern);
 24        }
 25        public Pattern getSyntax() {
 26            return syntax;
 27        }
 28        public static Command getMatchingCommand(String input) {
 29            commandMatcher.reset(input);
 30            Command[] allCommands = Command.values();
 31            for (Command command : allCommands) {
 32                if (commandMatcher.usePattern(command.getSyntax()).
                       matches()) {
 33                    return command;
 34                }
 35            }
 36            return null;
 37        }
 38    }
 39    public BankServer(Bank bank) {
 40        this.bank = bank;
 41        commandScanner = new Scanner(System.in);
 42    }
 43    public void service() throws NoSuchAccountException,
           NegativeAmountException {
 44        String input = commandScanner.nextLine();
 45        Command command = Command.getMatchingCommand(input);
 46        while (command != Command.QUIT) {
 47            int acno = 0;
 48            double amt = 0;
 49            char type = ’s’;
 50            String name = "";
 51
 52            if (command != null) {
 53                switch(command) {
 54                    case OPEN:
 55                        type = commandMatcher.group(2).charAt(0);
 56                        name = commandMatcher.group(3);
 57                        amt = Double.parseDouble(commandMatcher.
                              group(4));
 58                        switch(type) {
 59                           case ’S’:
 60                           case ’s’:
 61                               acno = bank.openSavingsAccount(name
                                      , amt);
 62                               break;
 63                           case ’C’:
 64                           case ’c’:
 65                               acno = bank.openCurrentAccount(name
                                      , amt);
 66                        }
 67                        bank.display(acno);
 68                        break;
 69                     case DEPOSIT:
 70                        acno = Integer.parseInt(commandMatcher.
                                group(2));
 71                        amt = Double.parseDouble(commandMatcher.
                                group(3));
 72                        bank.deposit(acno, amt);
 73                        bank.display(acno);
 74                        break;
 75                     case WITHDRAW:
 76                        acno = Integer.parseInt(commandMatcher.
                                group(2));
 77                        amt = Double.parseDouble(commandMatcher.
                                group(3));
 78                        if (bank.withdraw(acno, amt)) {
 79                            bank.display(acno);
 80                        } else {
 81                            System.out.println("Warning:
                                   Insufficient balance");
 82                        }
 83                        break;
 84                     case DISPLAY:
 85                        acno = Integer.parseInt(commandMatcher.
                                group(2));
 86                        bank.display(acno);
 87                        break;
 88                     case PASSBOOK:
 89                        acno = Integer.parseInt(commandMatcher.
                                group(2));
 90                        bank.printPassbook(acno);
 91                        break;
 92                     case LIST:
 93                        bank.listAccounts();
 94                        break;
 95                     case CLOSE:
 96                        acno = Integer.parseInt(commandMatcher.
                                group(2));
 97                        amt = bank.close(acno);
 98                        System.out.println(amt+" is the closing
                                balance");
 99                        break;
 100                }
 101            } else {
 102                System.out.println("Invalid command");
 103            }
 104            input = commandScanner.nextLine();
 105            command = Command.getMatchingCommand(input);
 106        }
 107    }
 108    public static void main(String[] args) throws Exception {
 109        String bankName = args[0];
 110        int bankCode = Integer.parseInt(args[1]);
 111        Bank bank = new Bank(bankName, bankCode);
 112        BankServer server = new BankServer(bank);
 113        server.service();
 114    }
 115 }
13.8 Varargs AND THE Formatter CLASS

Anyone who has done programming in C would have come across the printf() function. How many parameters does it have? The number of parameters for the printf() function is not fixed in C. It can be invoked by using variable number of arguments. From Java 5, we can have methods in Java with variable number of arguments. In fact, the printf() method is available in Java from Java 5 onwards. When we define a method, then only for the last argument, we may specify that there would be variable number of arguments of the specified type, e.g. the printf() method available in the PrintStream class (System.out is of type PrintStream) has been defined as given below:

    1     public void printf(String format, Object… values)

The last parameter has been declared as Object…. The elipses following the Object indicate that there can be any number of Object arguments following the first String argument, which may be used in the method invocation. Within the method all the values of the last parameter are available as an array. There can be zero or more occurences of the last parameter type, when the parameter is specified with elipses. The variable arguments cannot be specified for primitive types and they cannot be used for any parameter other than the last parameter. The printf() method of the PrintStream and PrintWriter does formatting exactly as per the rules for the format() method of the Formatter class.

13.8.1 Formatter Class

The Formatter class is used to achieve C’s printf() style formatting for various data types. The format() method of the Formatter class directly sends the formatted output to some destination. The various constructors require parameters which are either an Appendable or can be used to create an Appendable. The formatting is based on a format string which is used to specify the alignment and justification for common data types like the numeric data, date and time-related data and string data. The data types include the classes like the BigDecimal and Calendar. Some of the common constructors for the Formatter class are given in Listing 13.34.

 

Listing 13.34. Constructors for Formatter

    1     public Formatter()
 2     public Formatter(Appendable destination)
 3     public Formatter(String filename)
 4     public Formatter(String filename, String charactersetname)
 5     public Formatter(OutputStream out)
 6     public Formatter(OutputStream out, String charactersetname)

The default constructor creates an instance of StringBuilder where the formatted output will be stored. This StringBuilder instance can be accessed using the out() method. The common methods of the Formatter class are given in Listing 13.35.

 

Listing 13.35. Methods of Formatter

    1     public Formatter format(String formatString, Object… args)
 2     public Appendable out()
 3     public void flush()
 4     public void close()
 5     public String toString()

The format() method takes the first parameter as a String and is then followed by a variable number of arguments of any reference types. Because of the boxing conversions, even primitive types are also allowed to be used as the parameter. The format specifier in the first parameter is similar to the format specifier used in the printf() function of C. The printf() method of PrintStream and the PrintWriter classes work exactly like the format() method of the Formatter. There are some differences in the format specification for C language and the format specification used by the format() and the printf() methods in Java. The details of the format specification are available from the API documentation for the Formatter class.

EXERCISE 13.6 Update the printPassbook() method in the Account class of 13.3 to format the date of transaction using the format() method.

One of the possible implementations for the printPassbook() with formatting can be as given in Listing 13.36.

 

Listing 13.36. printPassbook() method with formatting

    1     public void printPassbook() {
 2         double runningBalance = 0;
 3         System.out.println("Passbook of "+name+" Account #
            "+accountNumer);
 4         for (Transaction t : passbook) {
 5              runningBalance += t.getNetAmount();
 6              System.out.printf("%1$te-%1$tb-%1$tY,%2$15s,%3$10.2f,%4
                     $10.2f,%5$10.2f
",
 7                                  t.getDateOfTransaction(),
 8                                  t.getNaration(),
 9                                  t.isCredit()?0:t.getAmount(),
 10                                 t.isCredit()?t.getAmount():0,
 11                                 runningBalance);
 12        }
 13        System.out.println("End of Passbook");
 14    }
LESSONS LEARNED
  • In Java, date and time are managed as time in milliseconds since 1st January, 1970, 0 hours GMT. Date, Calendar class and its sub-classes are normally used for managing date and time.
  • The Arrays class contains methods for most of the common operations required on all kinds of arrays.
  • The collection framework defines the functionality required by various kinds of data structures using interfaces. There are various kinds of implementations of these data structures. The collection framework helps in standardizing the usage of the data structure implementations and also promotes interoperability between these implementations.
  • The implementations of Set interface ensure unique (based on equals() method) collection of elements.
  • Java supports type parameters for classes and interfaces. These type parameters are used by the compiler and are not available at runtime. Generics support sub-typing using wild cards and constraints for the type parameters.
  • The Collections class contains methods for most of the common operations required for the collection framework.
  • For parsing of strings, there is a legacy class called StringTokenizer. The string parsing based on regular expressions is also supported with the help of Pattern and the Matcher classes.
  • The Scanner class can be used for parsing input from a stream. This stream is normally a character stream. It has standard methods for parsing standard data types from the stream and supports the use of a regular expression for parsing the input based on a pattern.
  • Java supports methods with variable number of arguments, which is allowed for the last parameter in a method or a constructor.
  • For formatting of various data types into character strings, Java has the Formatter class. The format() method in the Formatter class supports the formatting of various kinds of data types similar to the printf function of the C language.
EXERCISES
  1. State which of the following are true or false:
    1. Given that CurrentAccount is a sub-class of Account, List<CurrentAccount> is a sub-type of List<Account>.
    2. Generics are implemented by Type Erasure.
  2. Fill in the blanks in the following:
    1. The nextToken() method of StringTokenizer returns value of type __________.
    2. In the Date class, time in milliseconds is maintained relative to __________ GMT.
  3. Write an application to accept an email ID, and check if it is syntactically valid (use regular expression).
  4. What is the output generated by the code given below?

        1 import java.util.*;
     2
     3 public class Test {
     4     public static void main(String[] args) {
     5         StringTokenizer st = new StringTokenizer("Hello:abc,
                  xyz, 123",":");
     6         System.out.println(st.countTokens());
     7         String s = st.nextToken();
     8         System.out.println(st.countTokens());
     9         s = st.nextToken(",");
     10        System.out.println(st.countTokens());
     11    }
     12 }
    1. Compilation error
    2. 2
      1
      0
    3. 2
      1
      2
    4. 4
      3
      2
    5. None of the above
  5. What is the output of compiling and executing the following code:

        1 import java.util.*;
     2
     3 public class Test {
     4     public static void main(String[] args) {
     5         String inputString = "abc, xyz, mno, pqr";
     6         StringTokenizer st = new StringTokenizer(inputString,"
                   ,", true);
     7         System.out.println(st.countTokens());
     8     }
     9 }
    1. 4
    2. 3
    3. 7
    4. 8
    5. Error at runtime
  6. The java.util.List interface extends the java.util.Collection interface. Which of the following methods are in the List interface but not found in the Collection?
    1. public void set(Object element, int index)
    2. public boolean addAll(int index, Collection c)
    3. public void set(int index, Object element)
    4. public Iterator iterator()
    5. public Object get(int index)
    6. None of the above
  7. The java.util.Set interface extends the java.util.Collection interface. Which of the following methods are in the Set interface not found in the Collection?
    1. public boolean contains(Object element)
    2. public boolean addAll(Collection c)
    3. public Collection intersection(Collection c)
    4. public Iterator iterator()
    5. None of the above
  8. Consider a class as given below:

        1 import java.util.Scanner;
     2
     3 public class TestScanner {
     4     public static void main(String[] args) {
     5         Scanner scanner = new Scanner("7,8,9,10");
     6         scanner.useDelimiter(",").useRadix(8);
     7         System.out.println(scanner.nextInt());
     8         System.out.println(scanner.nextInt());
     9         System.out.println(scanner.nextInt());
     10        System.out.println(scanner.nextInt());
     11    }
     12 }

    What is the output of compiling and executing the code above?

    1. 7
      8
      9
      10
    2. 7
      10
      11
      12
    3. 7
      8
      9
      8
    4. Error at compile time
    5. Exception at runtime
  9. Write an application that reads three nonzero values entered by the user from the keyboard and determines and prints if they could represent the sides of the triangle. (The sum of two sides of a triangle is always greater than the third side.)
  10. Write an application that takes a line of text as input from the keyboard, tokenizes the line with an object of class StringTokenizer and outputs the tokens in reverse order.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.86.211