Chapter 22. Miscellaneous Utilities

 

The best way to make a fire with two sticks is to make sure one of them is a match.

 
 --Will Rogers

You will find several standard utility interfaces and classes in the java.util package. You have seen the collection classes already in Chapter 21. This chapter covers the remainder of the classes except those used for localization, which are in Chapter 24. The java.util classes covered in this chapter are

  • Formatter—. A class for producing formatted text.

  • BitSet—. A dynamically sized bit vector.

  • Observer/Observable—. An interface/class pair that enables an object to be Observable by having one or more Observer objects that are notified when something interesting happens in the Observable object.

  • Random—. A class to generate sequences of pseudorandom numbers.

  • Scanner—. A class for scanning text and parsing it into values of primitive types or strings, based on regular expression patterns.

  • StringTokenizer—. A class that splits a string into tokens based on delimiters (by default, whitespace).

  • Timer/TimerTask—. A way to schedule tasks to be run in the future.

  • UUID—. A class that represents a universally unique identifier (UUID).

Finally, we look at two other useful classes, housed in the java.lang package:

  • Math—. A class for performing basic mathematical operations, such as trigonometric functions, exponentiation, logarithms, and so on.

  • StrictMath—. Defines the same methods as Math but guarantees the use of specific algorithms that ensure the same results on every virtual machine.

Formatter

The Formatter class allows you to control the way in which primitive values and objects are represented as text. The common way to represent objects or values as text is to convert the object or value to a string, using either the toString method of the object or the toString method of the appropriate wrapper class. This is often done implicitly, either through use of string concatenation or by invoking a particular overload of the PrintStream or PrintWriterprint methods. This is easy and convenient, but it doesn't give you any control over the exact form of the string. For example, if you invoke

System.out.println("The value of Math.PI is " + Math.PI);

the output is

The value of Math.PI is 3.141592653589793

This is correct and informative, but it might provide more detail than the reader really needs to see, or more than you have room to show. Using a formatter you can control the format of the converted text, such as restricting the value to only three decimal places, or padding the converted string with spaces so that it occupies a minimum width regardless of the actual value being converted. All of these operations are possible starting from the result of toString, but it is much easier to have a formatter do it for you.

The primary method of a Formatter object is the format method. In its simplest form it takes a format string followed by a sequence of arguments, which are the objects and values you want to format. For convenience the PrintStream and PrintWriter classes provide a printf method (for “print formatted”) that takes the same arguments as format and passes them through to an associated Formatter instance. We use the System.out.printf method to demonstrate how format strings are used.

The format string contains normal text together with format specifiers that tell the formatter how you want the following values to be formatted. For example, you can print the value of Math.PI to three decimal places using

System.out.printf("The value of Math.PI is %.3f %n", Math.PI);

which prints

The value of Math.PI is 3.142

A format specifier starts with a % character and ends with a character that indicates the type of conversion to perform. In the example above, the f conversion means that the argument is expected to be a floating-point value and that it should be formatted in the normal decimal format. In contrast, an e conversion is a floating-point conversion that produces output in scientific notation (such as 3.142e+00). Other conversions include 'd for integers in decimal format, x for integers in hexadecimal, and s for strings or general object conversions. Conversion indicators that can produce non-digit text are defined in both a lowercase and uppercase form, such as e and E, where the uppercase form indicates that all text in the output will be converted to uppercase (as if the resulting String had toUpperCase invoked on it).

In addition to the conversion indicator, a format specifier can contain other values that control the layout of the converted value. In the example above the ".3" is a precision indicator, which for a floating-point decimal conversion indicates how many decimal places should appear in the result, with the value rounded up or down as appropriate. You can also control the width of the output text, ensuring that different formatted elements line up correctly (such as for printing data in a tabular form) regardless of the actual value being formatted. There are also flags that can be specified to control things like the justification (left or right) and the padding character to use to maintain the minimum width. The exact meaning of precision, width, and flags depends on the conversion being applied.

There are two special conversions that we quickly mention. The first is the % conversion used to output the % character. Because the % character marks the start of a format specifier, you need a way to include a % character that should actually appear in the output. The format specifier %% will do just that (just as the escape character \ is used to produce the single character ). You can specify a width with this conversion to pad the output with spaces: on the left if no flags are given, and on the right if the flag is given to request left-justification.

The second special conversion is the line separator conversion n. A format specifier of %n (as used in the example) outputs the platform specific line separator. The line separator string is defined by the system property line.separator and is not necessarily a single newline character ( )—see “System Properties” on page 663. Unlike println that outputs the line separator for you, printf and format require that you remember to do this yourself. No width, precision, or flag values can be used with the line separator conversion.

The following sections look at the format specifiers for numeric, character, and general conversions. The formatting conversions for dates and times are discussed in “Using Formatter with Dates and Times” on page 706. The Formatter class also supports formatting for instances of the java.math.BigInteger and java.math.BigDecimal classes, but those are not discussed here—consult the Formatter class documentation for information concerning those classes.

Format Specifiers

The general form of a format specifier for general, character, or numeric conversions is

%[argument_index][flags][width][.precision]conversion

where everything except the % and the conversion indicator are optional.

If there is an error in the format string, a mismatch between the conversion and the other formatting requests, or a mismatch between the conversion and the argument type supplied, then an IllegalFormatException of some form will be thrown. These exceptions are described later.

The argument index is an optional indicator of which argument this format specifier should be applied to. This is very useful for referring to the same argument multiple times within the same format string. The argument index can take two forms: a number indicating which argument it applies to, followed by a $ character; or the < character meaning “the same argument as the previous format specifier.” If the argument index is not present, then each unmarked format specifier is numbered sequentially, and it is that argument to which the format specifier is applied. This numbering ignores the presence of any indexed format specifiers. For example, given

System.out.printf("%3$d %d %2$d %<d %d %n", 1, 2, 3);

the output is

3 1 2 2 2

The first specifier explicitly applies to argument three; the second is the first non-indexed specifier so it applies to argument one; the third specifier explicitly applies to argument two; the fourth specifier applies to whatever the third did, so that is again two; the fifth specifier is the second non-indexed specifier so it also applies to argument two.

If any format specifier indicates an argument that does not exist, then you get a MissingFormatArgumentException. Examples of this kind of error are using %3$ when there are only two arguments, or using %< as the first format specifier.

The meaning of flags, width, and precision is similar for the different conversions but the details differ depending on the exact kind of conversion, as discussed in the following sections.

When you consider which argument types are expected for each conversion, remember that anything that applies to a primitive type also applies to its wrapper. For brevity we don't mention the wrapper classes explicitly.

Integer Conversions

The integer conversions apply to arguments of type byte, short, int, and long. The different conversions are

  • d—. decimal format

  • o—. octal format

  • x, X—. hexadecimal format

The width value specifies the minimum number of characters to output—including additional characters that the flags may cause to be included. No precision value may be given. The flags applicable to each conversion are shown in the following table. An entry with a × means the flag is not allowed.

Flag

d

o

x/X

Meaning

'-'

   

Left justify (otherwise right justify)

'#'

x

  

Include radix: 0 for octal and 0x or 0X for hexadecimal

'+'

 

x

x

Always include the sign

' '

 

x

x

(space) Include a leading space for positive values

'0'

   

Use zero-padding (else spaces)

','

 

x

x

Include grouping separators

'('

 

x

x

Enclose negative values in parentheses

For example, to print a zero-padded, hexadecimal value with a radix indicator and a minimum width of 10, you could use

System.out.printf("%0#10x %n", val);

which for val equal to 32 prints

0x00000020

You cannot use + and space together, nor can you use and 0 together. If either of 0 or is given then a width must be given.

Floating-Point Conversions

The floating-point conversions apply to arguments of type float and double. The different conversions are

  • e, E—computerized scientific notation (such as 3.142e+00)

  • f, F—decimal format (such as 3.142)

  • g, G—general scientific notation (see below)

  • a, A—hexadecimal exponential format (as used for hexadecimal floating-point literals—see page 168)

In each case a value of NaN or infinity produces the strings "NaN" or "Infinity".

The width value specifies the minimum number of characters to output, with the result being padded if necessary to fit the requested width. For everything except general scientific conversion, the precision value specifies the number of digits to output after the decimal point (or hexadecimal point), with the value rounded as necessary to match that precision. If no precision is given then it defaults to 6, except for the hexadecimal exponential form where no precision means to use as many places as needed.

A conversion to general scientific notation chooses the output format based on the magnitude of the value and the precision that is given. In this case precision indicates the number of significant digits (not the number of fractional digits). If after rounding to the specified precision, the value is smaller than 10-4, or it is greater than or equal to 10precision, then computerized scientific notation is used; otherwise, decimal format is used. For example, given

System.out.
    printf("%1$.5g %1$.4g %1$.3g %1$.2g %n", Math.PI * 100);

The output is:

314.16 314.2 314 3.1e+02

You can see the value being rounded according to the number of significant digits requested, and see the switch to scientific notation when the value exceeds 102.

For the general scientific notation conversion, if the precision is not given it defaults to 6, but a precision of zero is treated as a precision of 1.

The flags applicable to each conversion are shown in the following table. An entry with × means the flag is not allowed.

Flag

e/E

f/F

g/G

a/A

Meaning

'-'

    

Left justify (otherwise right justify)

'#'

  

x

 

Always include the (hexa)decimal point

'+'

    

Always include the sign

' '

    

(space) Leading space for positive values

'0'

    

Use zero-padding (else spaces)

','

x

  

x

Include grouping separators

'('

   

x

Enclose negative values in parentheses

As with the integer conversions, you cannot use + and space together, nor can you use and 0 together. If either of 0 or is given then a width must be given.

Character Conversions

The character conversions apply to argument types of byte, short, and char, together with int provided that the int value represents a valid Unicode code point (otherwise IllegalFormatCodePointException is thrown).

The conversion indicator for characters is c or C and formats the argument as a Unicode character.

A width value specifies the minimum number of characters to output, with spaces used to pad the output as necessary. No precision value may be supplied. The only applicable flag is – to left-justify the output. If the – flag is given then a width must be given.

General Conversions

There are three general conversions that are unrelated but that are not numeric or character conversions, so we describe them together. They apply to an argument of any type.

The b and B conversions are boolean conversions. If the argument is null then the output is the string "false". If the argument is a boolean then the output is either the string "true" or the string "false" depending on the argument's value, otherwise the output is the string "true".

The h and H conversions are hash code conversions. If the argument is null then the output is the string "null". Otherwise, the output is the string obtained by passing the object's hashCode result to Integer.toHexString.

The s and S conversions are string conversions. If the argument is null then the output is the string "null". Otherwise, if the argument implements the Formattable interface (see next section) then its formatTo method is invoked to produce a customized output; otherwise, the output is the result of invoking toString on the argument. The # flag can be passed only to a Formattable argument, and its effect is determined by the object.

For all the general conversions, the width specifies the minimum number of characters, while the precision indicates the maximum. Spaces will be used to pad the output to the minimum width if necessary. The precision is applied first so if it is smaller than the width, the output will be truncated to that maximum size. The only flag applicable is the flag to left-justify the output. If the flag is given then a width must be given.

Custom Formatting

A class can support custom formatting by implementing the Formattable interface, which defines the single method formatTo. If an instance of that class is passed as an argument for a string conversion (s or S) then its formatTo method will be invoked, passing the current formatter as an argument.

  • public void formatTo(Formatter formatter, int flags, int width, int precision)

    • Outputs the representation of the current object by appending the desired text to the destination of the given formatter, obtained from its out method (see Section 22.1.8 on page 631). The flags, width, and precision arguments contain the flags, width, and precision values that were used in the string conversion format specifier. If no width or precision was given, then –1 is passed. The flags are encoded as a bit mask from the constants of the FormattableFlags class. Only two flags are supported: LEFT_JUSTIFY, which indicates the flag for left-justification, and ALTERNATE, which indicates the # flag. Each Formattable class is free to define what ALTERNATE means.

Additionally, the flag value encodes whether the conversion indicator was lowercase or uppercase using the FormattableFlags.UPPERCASE constant. You can test whether a flag is set by ANDing the flags value with the desired constant and seeing if the result is non-zero.

By implementing Formattable a class can provide greater flexibility in how it represents itself textually, compared to the fixed output of the toString method. For example, it can use a long or short form, depending on the width and precision that were supplied; or it can adapt itself to the locale used by the formatter—see page 632.

Format Exceptions

If any errors occur while processing a format string with a given set of arguments, an exception that is a subclass of IllegalFormatException will be thrown. The types of exception you can encounter are

  • DuplicateFormatFlagsException—. A flag was used more than once.

  • FormatFlagsConversionMismatchException—. A flag was incompatible with the conversion.

  • IllegalFormatCodePointException—. An integer value was passed that was not a valid Unicode codepoint.

  • IllegalFormatConversionException—. The argument was the wrong type for the conversion.

  • IllegalFormatFlagsException—. The combination of flags was invalid.

  • IllegalFormatPrecisionException—. The precision value was invalid, or the conversion does not support a precision value.

  • IllegalFormatWidthException—. The width value was invalid, or the conversion does not support a width value.

  • MissingFormatArgumentException—. No argument was supplied in the position expected by a conversion modifier.

  • MissingFormatWidthException—. No width was specified when it was required.

  • UnknownFormatConversionException—. A format's conversion indicator was not known.

  • UnknownFormatFlagsExceptions—. An unknown flag was given.

The Formatter Class

A Formatter object is always associated with some object that is the destination for the formatted text produced by the formatter. By default the destination is a StringBuilder. The other destination objects that can be passed to the various constructors are

  • An arbitrary Appendable object. This is any object to which characters or CharSequence objects can be appended—see page 332 for the methods defined by Appendable.

  • A file, specified either as a File object or a string representing the file name

  • An OutputStream

The constructors for the byte-oriented destinations (files and output streams) either work with the platform's default encoding or take an additional string parameter that represents the character set encoding name to use. An additional constructor takes a PrintStream as a destination, but only the default platform encoding is used.

Formatter supports localization (see Chapter 24) and for each type of destination there is an additional constructor that takes a Locale object with which the formatter should work. If you do not specify a locale to the constructor, the default locale is used. The locale being used is returned by the locale method.

The destination of a Formatter object can be obtained from the out method, which returns an Appendable object.

If writing to the destination object could throw an IOException, then the last IOException to occur (if any) can be obtained from the ioException method.

Formatter implements Closeable and Flushable. If close or flush are invoked, then the destination is also closed or flushed if it too implements Closeable or Flushable. Once a formatter has been closed the only method that can be invoked is ioException; all others throw FormatterClosedException.

The primary method of Formatter is, of course, format:

  • public Formatter format(String format, Object... args)

    • Formats the given arguments according to the given format string and writes them through to the destination object. The current Formatter object is returned, allowing format invocations to be chained together.

  • public Formatter format(Locale loc, String format, Object... args)

    • Like the above format method, but uses the specified locale instead of the one from the current Formatter.

The Formatter class's support of localized output is restricted to a subset of the numeric conversions (and the date/time conversions discussed in Chapter 24). The localized numeric conversions are the integer decimal conversion (d) and the three main floating-point conversions (e, f, g and their uppercase counterparts). For these conversions, digit characters are taken from the active locale, and the correct sign characters, decimal point character, and grouping character (for the , flag) are also taken from the locale. Similarly, if zero-padding is requested the zero character is that defined by the locale. The NumberFormat class described in Chapter 24, on page 710, provides more extensive formatting capabilities for localized numbers.

Exercise 22.1Write a method that takes an array of floating-point values and a number indicating how many columns to use, and prints the array contents. Try to ensure that the entries in each column line up neatly. Assume a line is 80 characters wide.

BitSet

The BitSet class provides a way to create a bit vector that grows dynamically. In effect, a bit set is a vector of true or false bits indexed from 0 to Integer.MAX_VALUE, all of them initially false. These bits can be individually set, cleared, or retrieved. BitSet uses only sufficient storage to hold the highest index bit that has been set—any bits beyond that are deemed to be false.

Methods that take indices into the set throw IndexOutOfBoundsException if a supplied index is negative or, where relevant, if the from index is greater than the to index.

There are two constructors for BitSet:

  • public BitSet(int size)

    • Creates a new bit set with enough initial storage to explicitly represent bits indexed from 0 to size-1. All bits are initially false.

  • public BitSet()

    • Creates a new bit set with a default amount of initial storage. All bits are initially false.

There are four methods for dealing with individual bits.

  • public void set(int index)

    • Sets the bit specified by index to true.

  • public void clear(int index)

    • Sets the bit specified by index to false.

  • public void flip(int index)

    • Sets the bit specified by index to the complement of its current value—true becomes false, and false becomes true.

  • public boolean get(int index)

    • Returns the value of the bit specified by index.

A second overloaded form of each of the above methods works on a range of bits. These overloads take a from index and a to index and either sets, clears, flips, or returns all bits in the range, starting with from and up to but not including to. For get, the values are returned as a new BitSet. A third overload of clear takes no arguments and clears the entire set to false. A second variant of the set method takes both the index (or range) and a boolean value to apply to the bits. This makes it easier for you to change bits arbitrarily without having to work out whether you need to invoke set or clear.

You can find the index of the next clear or set bit, that is at or after a given index, using the nextClearBit and nextSetBit methods. If there is no next set bit from that index then –1 is returned.[1]

Other methods modify the current bit set by applying bitwise logical operations using the bits from another bit set:

  • public void and(BitSet other)

    • Logically ANDs this bit set with other and changes the value of this set to the result. The resulting value of a bit in this bit set is true only if it was originally true and the corresponding bit in other is also true.

  • public void andNot(BitSet other)

    • Clears all bits in this bit set that are set in other. The resulting value of a bit in this bit set is true only if it was originally true and the corresponding bit in other is false.

  • public void or(BitSet other)

    • Logically ORs this bit set with other and changes the value of this set to the result. The resulting value of a bit in this bit set is true only if it was originally true or the corresponding bit in other is true.

  • public void xor(BitSet other)

    • Logically XORs this bit set with other and changes the value of this set to the result. The resulting value of a bit in this bit set is true only if it has a value different from the corresponding bit in other.

You can also ask whether the current bit set has any true bits in common with a second bit set by using the intersects method.

The remaining methods are

  • public int cardinality()

    • Returns the number of bits in this bit set that are true.

  • public int size()

    • Returns the number of bits actually stored in this BitSet. Setting a bit index greater than or equal to this value may increase the storage used by the set.

  • public int length()

    • Returns the index of the highest set bit in this BitSet plus one.

  • public boolean isEmpty()

    • Returns true if this bit set has no true bits.

  • public int hashCode()

    • Returns a hash code for this set based on the values of its bits. Do not change values of bits while a BitSet is in a hash map, or the set will be misplaced.

  • public boolean equals(Object other)

    • Returns true if all the bits in other are the same as those in this set.

Here is a class that uses a BitSet to mark which characters occur in a string. Each position in the bit set represents the numerical value of a character: The 0th position represents the null character (u0000), the 97th bit represents the character a, and so on. The bit set can be printed to show the characters that it found:

public class WhichChars {
    private BitSet used = new BitSet();

    public WhichChars(String str) {
        for (int i = 0; i < str.length(); i++)
            used.set(str.charAt(i));    // set bit for char
    }

    public String toString() {
        String desc = "[";
        for (int i = used.nextSetBit(0);
             i >= 0;
             i = used.nextSetBit(i+1) ) {
            desc += (char) i;
        }
        return desc + "]";
    }
}

If we pass WhichChars the string "Testing123" we get back

[ 123Teginst]

which shows each of the characters (including the spaces) that were used in the input string, and which, incidentally, have now been sorted into numerical order. Notice how easy it is to iterate through all the set bits in a bit set.

Exercise 22.2The WhichChars class has a problem marking characters near the top of the Unicode range because the high character values will leave many unused bits in the lower ranges. Use a HashSet to solve this problem by storing Character objects for each character seen.

Exercise 22.3Now use a HashMap to store a BitSet object for each different top byte (high 8 bits) encountered in the input string, with each BitSet storing the low bytes that have been seen with the particular high byte.

Observer/Observable

The Observer/Observable types provide a protocol for an arbitrary number of Observer objects to watch for changes and events in any number of Observable objects. An Observable object subclasses the Observable class, which provides methods to maintain a list of Observer objects that want to know about changes in the Observable object. All objects in the “interested” list must implement the Observer interface. When an Observable object experiences a noteworthy change or an event that Observer objects may care about, the Observable object invokes its notifyObservers method, which invokes each Observer object's update method.

The Observer interface consists of a single method:

  • public void update(Observable obj, Object arg)

    • This method is invoked when the Observable object obj has a change or an event to report. The arg parameter is a way to pass an arbitrary object to describe the change or event to the observer.

The Observer/Observable mechanism is designed to be general. Each Observable class is left to define the circumstances under which an Observer object's update method will be invoked. The Observable object maintains a “changed” flag which subclass methods use to indicate when something of interest has occurred.

  • protected void setChanged()

    • Marks this object as having been changed—hasChanged will now return true—but does not notify observers.

  • protected void clearChanged()

    • Indicates that this object is no longer changed or has notified all observers of the last change—hasChanged will now return false.

  • public boolean hasChanged()

    • Returns the current value of the “changed” flag.

When a change occurs, the Observable object should invoke its setChanged method and then notify its observers with one of the following methods:

  • public void notifyObservers(Object arg)

    • Notifies all Observer objects in the list that something has happened, and then clears the “changed” flag. For each observer in the list, its update method is invoked with this Observable object as the first argument and arg as the second.

  • public void notifyObservers()

    • Equivalent to notifyObservers(null).

The following Observable methods maintain the list of Observer objects:

  • public void addObserver(Observer o)

    • Adds the observer o to the observer list if it's not already there.

  • public void deleteObserver(Observer o)

    • Deletes the observer o from the observer list.

  • public void deleteObservers()

    • Deletes all Observer objects from the observer list.

  • public int countObservers()

    • Returns the number of observers in the observer list.

The methods of Observable use synchronization to ensure consistency when concurrent access to the object occurs. For example, one thread can be trying to add an observer while another is trying to remove one and a third is effecting a change on the Observable object. While synchronization is necessary for maintaining the observer list and making changes to the “changed” flag, no synchronization lock should be held when the update method of the observers is invoked. Otherwise it would be very easy to create deadlocks. The default implementation of notifyObservers takes a synchronized snapshot of the current observer list before invoking update. This means that an observer that is removed while notifyObservers is still in progress will still be notified of the last change. Conversely, an observer that is added while notifyObservers is still in progress will not be notified of the current change. If the Observable object allows concurrent invocations of methods that generate notifications, it is possible for update to be called concurrently on each Observer object. Consequently, Observer objects must use appropriate synchronization within update to ensure proper operation.

The default implementation of notifyObservers uses the invoking thread to invoke update on each observer. The order in which observers are notified is not specified. A subclass could specialize notifyObservers to use a different threading model, and/or to provide ordering guarantees.

The following example illustrates how Observer/Observable might be used to monitor users of a system. First, we define a Users class that is Observable:

import java.util.*;

public class Users extends Observable {
    private Map<String, UserState> loggedIn = 
        new HashMap<String, UserState>();

    public void login(String name, String password)
        throws BadUserException
    {
        if (!passwordValid(name, password))
            throw new BadUserException(name);

        UserState state = new UserState(name);
        loggedIn.put(name, state);
        setChanged();
        notifyObservers(state);
    }

    public void logout(UserState state) {
        loggedIn.remove(state.name());
        setChanged();
        notifyObservers(state);
    }
   
     // ...

}

A Users object stores a map of users who are logged in and maintains UserState objects for each login. When someone logs in or out, all Observer objects will be passed that user's UserState object. The notifyObservers method sends messages only if the state changes, so you must invoke setChanged on Users; otherwise, notifyObservers would do nothing.

Here is how an Observer that maintains a constant display of logged-in users might implement update to watch a Users object:

import java.util.*;

public class Eye implements Observer {
    Users watching;

    public Eye(Users users) {
        watching = users;
        watching.addObserver(this);
    }
    public void update(Observable users, Object whichState)
    {
        if (users != watching)
            throw new IllegalArgumentException();

        UserState state = (UserState) whichState;
        if (watching.loggedIn(state))   // user logged in
            addUser(state);             // add to my list
        else
            removeUser(state);          // remove from list
    }
    // ...
}

Each Eye object watches a particular Users object. When a user logs in or out, Eye is notified because it invoked the Users object's addObserver method with itself as the interested object. When update is invoked, it checks the correctness of its parameters and then modifies its display depending on whether the user in question has logged in or out.

The check for what happened with the UserState object is simple here. You could avoid it by passing an object describing what happened and to whom instead of passing the UserState object itself. Such a design makes it easier to add new actions without breaking existing code.

The Observer/Observable mechanism is a looser, more flexible analogue to the wait/notify mechanism for threads described in “wait, notifyAll, and notify” on page 354. The thread mechanism ensures that synchronized access protects you from undesired concurrency. The observation mechanism enables any relationship to be built between two participants, whatever the threading model. Both patterns have producers of information (Observable and the invoker of notify) and consumers of that information (Observer and the invoker of wait), but each one fills a different need. Use wait/notify when you design a thread-based mechanism, and use Observer/Observable when you need something more general.

Exercise 22.4Provide an implementation of the Attributed interface that uses Observer/Observable to notify observers of changes.

Random

The Random class creates objects that manage independent sequences of pseudorandom numbers. If you don't care what the sequence is and want it as a sequence of double values, use the method java.lang.Math.random, which creates a single Random object the first time it is invoked and returns pseudorandom numbers from that object—see Section 22.9 on page 657. You can gain more control over the sequence (for example, the ability to set the seed) by creating a Random object and getting values from it.

  • public Random()

    • Creates a new random number generator. Its seed will be initialized to a value based on the current time.

  • public Random(long seed)

    • Creates a new random number generator using the specified seed. Two Random objects created with the same initial seed will return the same sequence of pseudorandom numbers.

  • public void setSeed(long seed)

    • Sets the seed of the random number generator to seed. This method can be invoked at any time and resets the sequence to start with the given seed.

  • public boolean nextBoolean()

    • Returns a pseudorandom uniformly distributed boolean value.

  • public int nextInt()

    • Returns a pseudorandom uniformly distributed int value between the two values Integer.MIN_VALUE and Integer.MAX_VALUE, inclusive.

  • public int nextInt(int ceiling)

    • Like nextInt(), but returns a value that is at least zero and is less than the value ceiling. Use this instead of using nextInt() and % to get a range. If ceiling is negative, an IllegalArgumentException is thrown.

  • public long nextLong()

    • Returns a pseudorandom uniformly distributed long value between Long.MIN_VALUE and Long.MAX_VALUE, inclusive.

  • public void nextBytes(byte[] buf)

    • Fills the array buf with random bytes.

  • public float nextFloat()

    • Returns a pseudorandom uniformly distributed float value between 0.0f (inclusive) and 1.0f (exclusive).

  • public double nextDouble()

    • Returns a pseudorandom uniformly distributed double value between 0.0 (inclusive) and 1.0 (exclusive).

  • public double nextGaussian()

    • Returns a pseudorandom Gaussian-distributed double value with mean of 0.0 and standard deviation of 1.0.

All the nextType methods use the protected method next. The next method takes an int that represents the number of random bits to produce (between 1 and 32) and returns an int with that many bits. These random bits are then converted to the requested type. For example, nextInt simply returns next(32), while nextBoolean returns true if next(1) is not zero, else it returns false.

You can safely use Random from multiple threads.

The Random class specifies the algorithms to be used to generate the pseudo-random numbers but permits different algorithms to be used provided the general contract of each method is adhered to. The basic algorithm (a linear congruential generator) is defined in the next method and is used for all other methods except nextGaussian. You can create your own random number generator by overriding the next method to provide a different generating algorithm.

Exercise 22.5Given a certain number of six-sided dice, you can calculate the theoretical probability of each possible total. For example, with two six-sided dice, the probability of a total of seven is one in six. Write a program that compares the theoretical distribution of sums for a given number of six-sided dice with the actual results over a large number of “rolls” using Random to generate numbers between one and six. Does it matter which of the number-generating methods you use?

Exercise 22.6Write a program that tests nextGaussian, displaying the results of a large number of runs as a graph (a bar chart of * characters will do).

Scanner

The Scanner class will help you read files of formatted data, such as those you might generate from a method that used printf. It uses regular expressions to locate the desired data, and parsers to convert them into known types. For example, it knows how to use localized number parsing to read in values that humans might type (such as “20,352”).

Many of the methods of Scanner can take a pattern to indicate how the scanner should match input. These methods all have two overloaded forms: One takes the pattern as a String and the other takes it as a java.util.regex.Pattern.When we say that a method takes a pattern, you should infer from this that there are two variants of the method as just described. Supplying a pattern as a string may require that it be compiled each time (using Pattern.compile), so if a pattern is to be reused it may be more efficient to use a Pattern object directly. See Section 13.3 on page 321 for a refresher on regular expression patterns.

There are two primary approaches to using Scanner, although they can be reasonably intermixed as desired, with care. The first is to use it to read a stream of values. The second is line-oriented.

Stream of Values

When reading input as a stream of values, you simply ask Scanner to return the next value in the stream. The scanner determines the next value by first skipping over any delimiter characters in the input (which by default matches whitespace) and examining the input up to the next delimiter. For example, the following code is a rewrite of the sumStream example from page 533:

static double sumStream(Readable source) throws IOException {
    Scanner in = new Scanner(source);
    double result = 0.0;
    while (in.hasNext()) {
        if (in.hasNextDouble())
            result += in.nextDouble();
        else
            in.next();
    }
    IOException ex = in.ioException();
    if (ex != null)
        throw ex;

    return result;
}

You can iterate through the input one token at a time, asking if the next token is a string, int, or double value, and so forth, and storing it into a variable of the appropriate type.[2] In contrast to StreamTokenizer, Scanner can produce primitive values other than just double, and it recognizes numbers in a range of formats, not just the common decimal format. (Essentially, how ever Formatter— see page 624—might write out a number as a string, Scanner can read it back as a number.)

A scanner needs a source of characters to read from, which in the most general case is represented by an object that implements the java.lang.Readable interface—this interface defines a single method, read, that takes a java.nio.CharBuffer object into which the characters read from the Readable should be stored. The sumStream method accepts a Readable parameter and passes it to the constructor for Scanner to indicate it should be used as the input source for that scanner. The java.io.Reader class implements Readable, so any of the input character streams can be used as a source for a Scanner object.

The loop continues as long as hasNext indicates that the scanner has another token available. Knowing that there is a token doesn't tell you what kind of token it may be, so we use the hasNextDouble method to ask if the token is a double value. If so, the value is read with nextDouble and added to the sum. Otherwise, the value is read (as a String) with next and ignored. The hasNext and next methods should be familiar to you—they are two of the methods of Iterator. And indeed Scanner implements Iterator<String> so you can easily iterate through the tokens in an input source.[3] The remove method of Scanner throws UnSupportedOperationException.

The hasNext method returns true if the scanner has another token to return from next. For each primitive type except char, hasNextType asks if the next token is of that type, and if so, nextType will return it. For the integer types there are overloads of hasNextType and nextType that take an int radix for the number. For example, hasNextInt(8) will return true if the next token is a valid octal representation of an int value. The default radix of the scanner (initially 10) can be changed with the useRadix method, and retrieved from the radix method.

The hasNext method also has two additional overloads that take a pattern and will return true only if the next token matches the given pattern. Similarly, next also has two overloads that take a pattern describing the token to return.

All the methods that get the next token operate as follows: Delimiters from the current position to the first non-delimiter are ignored; then the next delimiter is found; if the input between the delimiters matches the pattern then a token has been found and the current position is advanced to the first character after the token. If there is no next token then NoSuchElementException is thrown. If the method requires a specific type of token or a token that matches a given radix or pattern, and the next token does not meet those criteria, then InputMismatchException is thrown. If an exception is thrown then the position of the scanner is unchanged.

Input sources for scanners can come from several places. There is a constructor that takes a Readable character source, one that takes a String, and several that take byte sources: File, InputStream or java.nio.ReadableByteBuffer. By default these byte sources will be converted to characters using the platform's default encoding, or you can use a form of the constructor that takes the character set name to use as the encoding.

When the source for a scanner is a file or other input stream, it is possible that when the scanner tries to read the source it will encounter an IOException. The scanner methods themselves do not declare or throw the IOException. The responsibility for dealing with these potential exceptions falls to you. If the scanner encounters an IOException it considers the end of the input source to have been reached—so hasNext will return false, and any attempt to take the next token will cause NoSuchElementException to be thrown. To determine whether tokenizing of the source ended because of a true end of input or an exception, you can use the ioException method. It returns the last IOException that was thrown by the underlying source, or null if there have been no exceptions. So, as in the example, when the iteration is complete you should check to see that it didn't complete prematurely due to an exception.

A scanner identifies tokens by looking for a delimiter pattern in the input stream of characters. By default the delimiter pattern matches any whitespace, as determined by the Character.isWhitespace method. You can set the delimiter to a different pattern with the useDelimiter method. The current delimiter pattern is returned as a Pattern object from the delimiter method. For example, here is a simple method that reads values from a source that is in comma-separated-values (CSV) format and returns them in a List:

public static List<String> readCSV(Readable source)
    throws IOException {
    Scanner in = new Scanner(source);
    in.useDelimiter(",|" +LINE_SEPARATOR_PATTERN);
    List<String> vals = new ArrayList<String>();

    while (in.hasNext())
        vals.add(in.next());

    IOException ex = in.ioException();
    if (ex!= null)
        throw ex;

    return vals;
}

Here the delimiter pattern is either a single comma or a line separator, since the values themselves can contain any other characters, including whitespace. The definition of a line separator is documented in the Pattern class. To make it easier to read the example, we defined a constant to represent that pattern:

static final String LINE_SEPARATOR_PATTERN =
    "
|[

u2028u2029u0085]";

The Scanner class also provides a close method. When you invoke close on a scanner, the input source will be closed if it is Closeable. Once a scanner has been closed, attempts to invoke any of the scanning operations will result in an IllegalStateException. Take care when closing a scanner: The input source may not be yours to close, such as an InputStreamReader for System.in. As a general rule, don't close a scanner unless you opened its input source.

Scanning Lines

In line mode, the scanner can process a line at a time using the regular expression pattern that you supply. If the line matches the pattern then a java.util.regex.MatchResult object can be obtained from the match method, from which you can extract the groups the pattern defined. Line matching is done with the findInLine method.

  • public String findInLine(Pattern pattern)

    • Attempts to find the given pattern before the next line separator is encountered. The delimiters of the scanner are ignored during this search. If the pattern is found then the scanner advances to the next position after the matching input, and the matching input is returned. If the pattern is not found then null is returned and the position of the scanner is unchanged.

Note that if the pattern does not match the entire line then the scanner will be positioned to continue reading the remainder of the current line; and if the pattern starts in the middle of the line, then all preceding input is skipped. As usual a second form of this method takes a String representing the pattern.

You can use a scanner's hasNextLine and nextLine methods to tokenize input a complete line at a time. The hasNextLine method returns true if there is another line of input in this scanner. This means that between the current position of the scanner and the end of the input there is either a line separator or one or more other characters. The nextLine method advances the scanner to the start of the next line and returns all the input that was skipped, excluding the line separator itself.

Consider the example of input that is in comma-separated-value format. This format is often used to export values from a table, where each line in the output represents a row of the table and each value before a comma (or a newline) represents the contents of a cell. With a fixed number of cells per row we can process each line of input as a single entity:

static final int CELLS = 4;

public static List<String[]> readCSVTable(Readable source)
    throws IOException {
    Scanner in = new Scanner(source);
    List<String[]> vals = new ArrayList<String[]>();
    String exp = "^(.*),(.*),(.*),(.*)";
    Pattern pat = Pattern.compile(exp, Pattern.MULTILINE);
    while (in.hasNextLine()) {
        String line = in.findInLine(pat);
        if (line != null) {
            String[] cells = new String[CELLS];
            MatchResult match = in.match();
            for (int i = 0; i < CELLS; i++)
                cells[i] = match.group(i+1);
            vals.add(cells);
            in.nextLine();  // skip newline
        }


        else {
            throw new IOException("input format error");
        }
    }

    IOException ex = in.ioException();
    if (ex!= null)
        throw ex;

    return vals;
}

Each line of the input is expected to have four values separated by commas (naturally you'd want to adapt the pattern to account for different numbers of cells in different calls to the method). If the line matches what is expected then each value is extracted from the match group. When a match is found, the newline is left in the input so we use nextLine to skip over it. As we don't know how many lines there will be, we use a List to accumulate the rows.

The use of a pattern in multiline mode is common when scanning line-oriented input. The pattern we used has to match from the start of a line, so we need to include the start-of-line (^) boundary marker, and that only matches actual lines when the pattern is in multiline mode.

The findWithinHorizon methods operate similarly to findInLine except that they take an additional int parameter that specifies the maximum number of characters to look-ahead through. This “horizon” value is treated as a transparent, non-anchoring bound (see the Matcher class for details—Section 13.3.4 on page 329). A horizon of zero means there is no look-ahead limit.

The skip method can be used to skip over the input that matches a given pattern. As with findInLine and findWithinHorizon, it ignores the scanner's delimiters when looking for the pattern. The skipped input is not returned, rather skip returns the scanner itself so that invocations can be chained together.

Exercise 22.7Rewrite readCSVTable so that the number of cells of data expected is passed as an argument.

Exercise 22.8As it stands, readCSVTable is both too strict and too lenient on the input format it expects. It is too strict because an empty line at the end of the input will cause the IOException to be thrown. It is too lenient because a line of input with more than three commas will not cause an exception. Rectify both of these problems.

Exercise 22.9Referring back to the discussion of efficiency of regular expressions on page 329, devise at least four patterns that will parse a line of comma-separated-values. (Hint: In addition to the suggestion on page 329 also consider the use of greedy versus non-greedy quantifiers.) Write a benchmark program that compares the efficiency of each pattern, and be sure that you test with both short strings between commas and very long strings.

Using Scanner

Scanner and StreamTokenizer have some overlap in functionality, but they have quite different operational models. Scanner is based on regular expressions and so can match tokens based on whatever regular expressions you devise. However, some seemingly simple tasks can be difficult to express in terms of regular expression patterns. On the other hand, StreamTokenizer basically processes input a character at a time and uses the defined character classes to identify words, numbers, whitespace, and ordinary characters. You can control the character classes to some extent, but you don't have as much flexibility as with regular expressions. So some things easily expressed with one class are difficult, or at best awkward, to express with the other. For example, the built-in ability to handle comment lines is a boon for StringTokenizer, while using a scanner on commented text requires explicit, unobvious handling. For example:

Scanner in = new Scanner(source);
Pattern COMMENT = Pattern.compile("#.*");
String comment;
// ...
while (in.hasNext()) {
    if (in.hasNext(COMMENT)) {
        comment = in.nextLine();
    }
    else {
        // process other tokens
    }
}

This mostly works. The intent is that if we find that the next token matches a comment, then we skip the rest of the line by using nextLine. Note that you can't, for example, use a pattern of "#.*$" to try to match from the comment character to the end of the line, unless you are guaranteed there are no delimiters in the comment itself.

The above fails to work because it can act like there were two comments when there was only one. Consider a non-comment line followed by a comment line. The input stream might look something like this:

token
# This is a comment line
token2

After the last token on the non-comment line is processed, the current input position is just before the line separator that delimited the end of that token. When hasNext is invoked it looks past the line separator and sees that there is something there, so it returns true, leaving the current input position where it was. When hasNext(COMMENT) is invoked, it too ignores the line separator delimiter and sees a pattern on the next line that matches the comment, so it also returns true, leaving the current input position where it was. When nextLine is invoked its job is to advance the current position to the beginning of the next line and return the input that was skipped. The current position is immediately before the line separator, so moving it after the line separator is a very short move and, other than the line separator, no input is skipped. Consequently, nextLine returns an empty string. In our example this is not a problem because we loop around again and match the comment a second time, and this time we remove it properly. However, if your code assumed that the comment itself was gone after nextLine was invoked, then that assumption would be incorrect. We can fix this problem like so:

Scanner in = new Scanner(source);
Pattern COMMENT = Pattern.compile("#.*");
String comment;
// ...
while (in.hasNext()) {
    if (in.hasNext(COMMENT)) {
        comment = in.findWithinHorizon(COMMENT, 0);
        in.nextLine();
    }
    else {
        // process other tokens
    }
}

Now when hasNext(COMMENT) tells us that there is a comment ahead, we use findWithinHorizon(COMMENT,0) to skip the line separator, find the actual comment, and return it. We don't need to set any horizon because we know from hasNext that the comment is there. After findWithinHorizon returns the comment, the current position is just before the line separator at the end of the comment line, so we use nextLine to skip over that to the next line.

Another way to get the scanner to skip comments would be to make the comment pattern part of the delimiter pattern. But that is not quite as straightforward as it might sound. We leave that approach as an exercise for the reader.

So skipping comments was trivial with StreamTokenizer and quite involved with Scanner. In contrast, it is quite simple to change the scanner's delimiter to parse a comma-separated-variable file, but to do the same with StringTokenizer requires careful manipulation of the character classes to make the comma a whitespace character and to stop space from being considered a whitespace character. Although relatively simple to state, the API makes it awkward to do and it is conceptually bizarre.

StreamTokenizer is very good for working with free-format files such as that used in the attribute reading example on page 533. It read input that consisted of names and values, seperated by whitespace, with an optional = character in between, and stored them into an Attr object. The names were simply words, and values could be words or numbers, while the = character was an ordinary character. Pairing of names and values was trivially done, as was detecting a misplaced = character. In contrast, Scanner has a lot of trouble dealing with such a flexible format. If you add the = character to the delimiter it isn't too hard to simply treat every two words as a name and value pair. However, because delimiters are ignored, you can't detect misplaced = characters. If you don't make the = character a delimiter, then you have problems when there is no whitespace between the = and the name or value.

If you wanted to store Attr objects and read them back with a Scanner, you could have more flexibility with the values than if you used StreamTokenizer, but the file would be more restricted in format. For example, here is a pair of methods to print and scan attributes:

public static void printAttrs(Writer dest, Attr[] attrs) {
    PrintWriter out = new PrintWriter(dest);
    out.printf("%d attrs%n", attrs.length);
    for (int i = 0; i < attrs.length; i++) {
        Attr attr = attrs[i];
        out.printf("%s=%s%n",
                   attr.getName(), attr.getValue());
    }
    out.flush();
}

public static Attr[] scanAttrs(Reader source) {
    Scanner in = new Scanner(source);
    int count = in.nextInt();
    in.nextLine();    // skip rest of line


    Attr[] attrs = new Attr[count];
    Pattern attrPat =
        Pattern.compile("(.*?)=(.*)$", Pattern.MULTILINE);
    for (int i = 0; i < count; i++) {
        in.findInLine(attrPat);
        MatchResult m = in.match();
        attrs[i] = new Attr(m.group(1), m.group(2));
    }
    return attrs;
}

The printAttrs method uses printf to print an attribute in one line, using a = character to separate name from value. The scanAttrs method will read such a file back in. It uses a combination of stream-based and line-based Scanner calls. It first reads back the count as an int and then consumes the rest of the line (the “attrs” from the printf is just for human readability). It then loops getting name/value lines. The pattern used has two capture groups, the first to get the attribute name (using a non-greedy qualifier to get the fewest possible characters before the =) and the second to get the value. These groups are pulled out to create the actual Attr object.

It is no accident that these two methods are paired. Although a Scanner can read many formats of data, it is best used for data formatted in relatively straightforward ways. Stream mode is useful for reading user input as well, but when you use Scanner for data you should be able to visualize (if not actually write) the printf usage that would generate that data. Scanner is extremely powerful, but that power can be difficult to harness.

Exercise 22.10Write a method to tokenize input that ignores comments, using the comment pattern as part of the scanner's delimiter.

Exercise 22.11Write a version of readCSV that uses a StreamTokenizer rather than a Scanner.

Exercise 22.12Write a version of the attribute reading method from page 533 that uses a Scanner. For this exercise it is only necessary that both versions accept the same correctly formatted input.

Exercise 22.13Extend your solution to Exercise 22.12 so that misplaced = characters are detected, as in the StreamTokenizer version. (Hint: You might want to try to dynamically change the delimiter pattern between certain tokens.)

Localization

Localization is covered in detail in Chapter 24, but since the Scanner class has some support for localization, we cover that briefly here.

By default a scanner works in the current locale, but you can change it with the useLocale method, which takes a Locale instance. The locale being used by the scanner can be retrieved from the locale method.

The type-specific numeric scanning methods, like hasNextDouble and nextInt, are fully localized. They recognize numerical values that are formed from the numeric digit characters of the locale being used, including the locale-specific decimal separator and grouping separator. In contrast, to use a regular expression to match a local number, you would need to know what the separator characters were so that you could account for them in your pattern. Further, any potentially numeric value that is obtained as a string from a MatchResult must be separately converted into a numeric value. To localize such numeric values you must use the parsing methods of the java.text.NumberFormat class described in Chapter 24.

StringTokenizer

The StringTokenizer class is an older and much simpler cousin of the Scanner class, and its use is discouraged in new code—the Stringsplit method (page 314) can be used as an alternative in many cases. A StringTokenizer breaks a string into parts, using delimiter characters. A sequence of tokens broken out of a string is, in effect, an ordered enumeration of those tokens, so StringTokenizer implements the Enumeration interface[4] (see page 617). StringTokenizer provides methods that are more specifically typed than Enumeration, which you can use if you know you are working on a StringTokenizer object. The StringTokenizer enumeration is effectively a snapshot because String objects are read-only. For example, the following loop breaks a string into tokens separated by spaces or commas:

String str = "Gone, and forgotten";
StringTokenizer tokens = new StringTokenizer(str, " ,");
while (tokens.hasMoreTokens())
    System.out.println(tokens.nextToken());

By including the comma in the list of delimiter characters in the StringTokenizer constructor, the tokenizer consumes commas along with spaces, leaving only the words of the string to be returned one at a time. The output of this example is

Gone
and
forgotten

The StringTokenizer class has several methods to control what is considered a word, whether it should understand numbers or strings specially, and so on:

  • public StringTokenizer(String str, String delim,boolean returnTokens)

    • Constructs a StringTokenizer on the string str, using the characters in delim as the delimiter set. The returnTokens boolean determines whether delimiters are returned as tokens or skipped. If they are returned as tokens, each delimiter character is returned separately.

  • public StringTokenizer(String str, String delim)

    • Equivalent to StringTokenizer(str, delim, false), meaning that delimiters are skipped, not returned.

  • public StringTokenizer(String str)

    • Equivalent to StringTokenizer(str," f"), meaning that the delimiters are the whitespace characters and are skipped.

  • public boolean hasMoreTokens()

    • Returns true if more tokens exist.

  • public String nextToken()

    • Returns the next token of the string. If there are no more tokens, a NoSuchElementException is thrown.

  • public String nextToken(String delim)

    • Switches the delimiter set to the characters in delim and returns the next token. There is no way to set a new delimiter set without getting the next token. If there are no more tokens, a NoSuchElementException is thrown.

  • public int countTokens()

    • Returns the number of tokens remaining in the string using the current delimiter set. This is the number of times nextToken can return before it will generate an exception. When you need the number of tokens, this method is faster than repeatedly invoking nextToken because the token strings are merely counted, not constructed and returned.

The methods StringTokenizer implements for the Enumeration interface (hasMoreElements and nextElement) are equivalent to hasMoreTokens and nextToken, respectively.

The delimiter characters are processed individually when StringTokenizer looks for the next token, so it cannot identify tokens separated by a multicharacter delimiter. If you need a multicharacter delimiter, use either the String class's split method or a Scanner.

Exercise 22.14Write a method that will take a string containing floating-point numbers, break it up using whitespace as the delimiter, and return the sum of the numbers.

Timer and TimerTask

The Timer class helps you set up tasks that will happen at some future point, including repeating events. Each Timer object has an associated thread that wakes up when one of its TimerTask objects is destined to run. For example, the following code will set up a task that prints the virtual machine's memory usage approximately once a second:

Timer timer = new Timer(true);
timer.scheduleAtFixedRate(new MemoryWatchTask(), 0, 1000);

This code creates a new Timer object that will be responsible for scheduling and executing a MemoryWatchTask (which you will see shortly). The true passed to the Timer constructor tells Timer to use a daemon thread (see page 369) so that the memory tracing activity will not keep the virtual machine alive when other threads are complete.

The scheduleAtFixedRate invocation shown tells timer to schedule the task starting with no delay (the 0 that is the second argument) and repeat it every thousand milliseconds (the 1000 that is the third argument). So starting immediately, timer will invoke the run method of a MemoryWatchTask:

import java.util.TimerTask;
import java.util.Date;

public class MemoryWatchTask extends TimerTask {
    public void run() {
        System.out.print(new Date() + ": " );
        Runtime rt = Runtime.getRuntime();
        System.out.print(rt.freeMemory() + " free, ");
        System.out.print(rt.totalMemory() + " total");
        System.out.println();
    }
}

MemoryWatchTask extends the abstract TimerTask to define a task that prints the current free and total memory, prefixed by the current time. TimerTask implements the Runnable interface, and its run method is what is invoked by a Timer object when a task is to be run. Because the setup code told timer to execute once a second, the thread used by timer will wait one second between task executions.

TimerTask has three methods:

  • public abstract void run()

    • Defines the action to be performed by this TimerTask.

  • public boolean cancel()

    • Cancels this TimerTask so that it will never run again (or at all if it hasn't run yet). Returns true if the task was scheduled for repeated execution or was a once-only task that had not yet been run. Returns false if the task was a once-only task that has already run, the task was never scheduled, or the task has previously been cancelled. Essentially, this method returns true if it prevented the task from having a scheduled execution.

  • public long scheduledExecutionTime()

    • Returns the scheduled execution time of the most recent actual execution (possibly the in-progress execution) of this TimerTask. The returned value represents the time in milliseconds. This method is most often used inside run to see if the current execution of the task occurred soon enough to warrant execution; if the task was delayed too long run may decide not to do anything.

You can cancel either a single task or an entire timer by invoking the cancel method of TimerTask or Timer. Cancelling a task means that it will not be scheduled in the future. Cancelling a Timer object prevents any future execution of any of its tasks. If you purge a timer then all references to cancelled tasks are removed from its internal queue of tasks to schedule.

Each Timer object uses a single thread to schedule its task executions. You can control whether this thread is a daemon thread by the boolean you specify to the Timer constructor.

  • public Timer(boolean isDaemon)

    • Creates a new Timer whose underlying thread has its daemon state set according to isDaemon.

  • public Timer()

    • Equivalent to Timer(false).

  • public Timer(String name)

    • Equivalent to Timer(false) but gives the associated thread the given name. This is useful for debugging and monitoring purposes.

  • public Timer(String name, boolean isdaemon)

    • Equivalent to Timer(isDaemon) but gives the associated thread the given name. This is useful for debugging and monitoring purposes.

If the thread is not specified to be a daemon, it will be a user thread. When the timer is garbage collected—which can only happen when all references to it are dropped and no tasks remain to be executed—the user thread will terminate. You should not rely on this behavior since it depends on the garbage collector discovering the unreachability of the timer object, which can happen anytime or never.

There are three kinds of task scheduling. A once-only scheduling means that the task will be executed once. A fixed-delay scheduling lets you define the amount of time between the start of one execution and the next. The task essentially executes periodically, but any delay in the start of one execution (due to general thread scheduling considerations, garbage collection, or other background activity) causes the next execution to be similarly delayed, so that an execution starts relative to the start of the previous execution. In contrast, a fixed-rate scheduling starts each execution relative to the start of the initial execution. If one execution is delayed, there will be a shorter gap before the next execution—possibly no gap, depending on the extent of the delay. You use fixed-delay scheduling when the frequency of tasks is what matters, such as time delays between animation frames in some circumstances. You use fixed-rate scheduling when the absolute time matters, such as alarms or timers.

  • public void schedule(TimerTask task, Date time)

    • Schedules the given task for a once-only execution at the specified time.

  • public void schedule(TimerTask task, long delay)

    • Schedules the given task for a once-only execution after the specified delay (in milliseconds),

  • public void schedule(TimerTask task, Date firstTime, long period)

    • Schedules the given task to execute on a fixed-delay schedule until cancelled, starting at firstTime, executing every period milliseconds.

  • public void schedule(TimerTask task, long delay, long period)

    • Schedules the given task to execute on a fixed-delay schedule until cancelled, starting after the given delay, executing every period milliseconds.

  • public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)

    • Schedules the given task to execute on a fixed-rate schedule until cancelled, starting at firstTime, executing every period milliseconds.

  • public void scheduleAtFixedRate(TimerTask task, long delay, long period)

    • Schedules the given task to execute on a fixed-rate schedule until cancelled, starting after the given delay, executing every period milliseconds.

Any time you specify that is in the past schedules an immediate execution. All times are in milliseconds—the Date class's getTime method converts a Date to milliseconds (see “Time, Dates, and Calendars” on page 695)—and are approximate because the timer uses Thread.wait(long) to schedule future executions, and wait, like sleep, does not guarantee precision. If a delay is so large that adding it to the current time would cause an overflow, you will get an IllegalArgumentException.

A TimerTask object can only be scheduled with one Timer, and a cancelled Timer cannot have any new tasks scheduled. If you attempt to schedule a task that violates either of these restrictions or if you schedule an already cancelled TimerTask, you will get an IllegalStateException.

A Timer's thread is subject to the usual thread scheduling of a system and takes no steps to influence its priority in any way—it is created with the priority of the thread that created the Timer. If you need to boost the priority of a given task when it is executing, set the thread priority within the run method. If you want to boost the priority of the Timer thread itself, you must create the Timer from a thread that already has the desired priority level.

There is no way to ask which tasks are being governed by a particular Timer.

UUID

The UUID class provides immutable objects that represent universally unique identifiers. These are 128-bit values that are generated in such a way that they are guaranteed to be unique. There are four different versions of UUID values, generally known as types 1, 2, 3, and 4. You can ask a UUID object for its version by using the version method.

The usual way to create a type 4 (random) UUID is to use the static factory method randomUUID. A type 3 (name-based) UUID can be obtained from the static factory method nameUUIDFromBytes, which takes an array of bytes representing the name.

You can directly construct an arbitrary form UUID by supplying two long values that represent the upper and lower halves. Of course, there are no guarantees about uniqueness for such a constructed UUID—it is up to you to supply appropriate values. The two halves of a UUID object can be retrieved as long values from the getMostSignificantBits and getLeastSignificantBits methods.

The layout of a UUID is determined by which variant is used to implement it. There are four variants: 0, 2, 6, and 7. The UUID class always generates variant 2 UUID values—known as the Leach-Salz variant. The details of these variants are not important for this discussion. You can ask a UUID object for its variant by using the variant method. Two UUID objects are equal only if they have the same variant and 128-bit value.

The toString method returns a string representation of a UUID that can be passed into the static fromString factory method to get a UUID object.

The remaining methods are applicable only to type 1 (time-based) UUID values, and throw UnsupportedOperationException if invoked on other types:

  • public long timestamp()

    • Returns the 60-bit timestamp associated with this UUID.

  • public int clockSequence()

    • Returns the 14-bit clock sequence value associated with this UUID.

  • public long node()

    • Returns the 48-bit node value associated with this UUID. This value relates to the host address of the machine on which this UUID was generated.

Math and StrictMath

The Math class consists of static constants and methods for common mathematical manipulations that use normal floating-point semantics. The StrictMath class defines the same constants and methods but always uses specific algorithms. This means that StrictMath methods always return the same values across different virtual machines, while Math methods are allowed to vary within specified limits. Both classes have two useful double constants: E approximates e (2.7182818284590452354), and PI approximates π (3.14159265358979323846). In the following table of the main Math and StrictMath methods, angles are in radians and all parameters and return values are double unless stated otherwise:

Function

Value

sin(a)

sine(a)

cos(a)

cosine(a)

tan(a)

tangent(a)

asin(v)

arcsine(v), with v in the range [–1.0, 1.0]

acos(v)

arccosine(v), with v in the range [–1.0, 1.0]

atan(v)

arctangent(v), returned in the range [–π/2,π/2]

atan2(x,y)

arctangent(x/y), returned in the range [–π,π]

toRadians(d)

Given d in degrees, returns equivalent angle in radians

toDegrees(r)

given r in radians, returns equivalent angle in degrees

exp(x)

e x

sinh(x)

hyperbolic sine of x

cosh(x)

hyperbolic cosine of x

tanh(x)

hyperbolic tangent of x

pow(y,x)

y x

log(x)

ln x (natural log of x)

log10(x)

base 10 logarithm of x

sqrt(x)

Square root of x

cbrt(x)

Cubic root of x

signum(x)

signum function: 1 if x is positive; –1 if x is negative; 0 if x is zero. There are double and float versions

ceil(x)

Smallest whole number ≥ x

floor(x)

Largest whole number ≤ x

rint(x)

x rounded to the nearest integer; if neither integer is nearer, rounds to the even integer

round(x)

(int) floor(x + 0.5) for float x (long) floor(x + 0.5) for double x

abs(x)

| x | for any numeric type. (the absolute value of the most negative value of an int or long is itself and therefore negative; that's how two's complement integers work)

max(x,y)

Larger of x and y for any numeric type

min(x,y)

Smaller of x and y for any numeric type

hypot(x,y)

Calculates the length of the hypotenuse of a right-angled triangle with sides of length x and y; that is, it calculates Math and StrictMath with no intermediate overflow or underflow

The static method IEEEremainder calculates remainder as defined by the IEEE 754 standard. The remainder operator %, as described in “Floating-Point Arithmetic” on page 202, obeys the rule

(x/y)*y + x%y == x

This preserves one kind of symmetry: Symmetry around zero. That is, if x%y is z, then changing the sign of either x or y will change only the sign of z, never its absolute value. For example, 7%2.5 is 2.0, and -7%2.5 is -2.0. The IEEE standard defines remainder for x and y differently, preserving symmetry of spacing along the number line—the result of IEEEremainder(-7,2.5) is 0.5. The IEEE remainder mechanism keeps resulting values y units apart, as contrasted with the remainder operator's symmetry around zero. The method is provided because both kinds of remainder are useful.

The static method random generates a pseudorandom number r in the range 0.0 ≤ r < 1.0. More control over pseudorandom number generation is provided by the Random class, as you learned on page 639.

The algorithms used in StrictMath are those defined in the FDLIBM “C” math library; if you need a complete understanding of these issues, consult The Java Language Specification, Third Edition.

See “java.math — Mathematics” on page 722 for brief coverage of some other math-related classes.

Exercise 22.15Write a calculator that has all Math or StrictMath functions, as well as (at least) the basic operators +, -, *, /, and %. (The simplest form is probably a reverse Polish stack calculator because operator precedence is not an issue.)

 

Computers are useless—they can only give you answers.

 
 --Pablo Picasso


[1] The only time there can be no next clear bit is if the bit at index Integer.MAX_VALUE has been set—something that is extremely unlikely in practice because the bit set could use a huge amount of memory

[2] The Scanner class also supports scanning of values into java.math.BigInteger and java.math.BigDecimal objects.

[3] Note that Scanner is an Iterator not an Iterable, so it can not be used with the enhanced for loop. This limitation may be removed in the future.

[4] For historical reasons it implements Enumeration<Object>, not Enumeration<String>.

..................Content has been hidden....................

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