Legacy Collections in Java

A number of “legacy” container classes have been present since the first release of Java, before there was a collections framework.

They have been integrated into the collections framework—see Figure 9.12. We briefly introduce them in the following sections.

1. The Hashtable Class

The classic Hashtable class serves the same purpose as the HashMap class and has essentially the same interface. Just like methods of the Vector class, the Hashtable methods are synchronized. If you do not require compatibility with legacy code, you should use a HashMap instead. If you need concurrent access, use a ConcurrentHashMap—see Chapter 12.

2. Enumerations

The legacy collections use the Enumeration interface for traversing sequences of elements. The Enumeration interface has two methods, hasMoreElements and nextElement. These are entirely analogous to the hasNext and next methods of the Iterator interface.

If you find this interface with legacy classes, you can use Cotlections.tist to collect the elements in an ArrayList. For example, the LogManager class is only willing to reveal logger names as an Enumeration. Here is how you can get them all:

ArrayList<String> toggerNames = Cottections.tist(LogManager.getLoggerNames());

Alternatively, as of Java 9, you can turn an enumeration into an iterator:

LogManager.getLoggerNames().asIterator().forEachRemaining(n -> { . . . });

You will occasionally encounter a legacy method that expects an enumeration parameter. The static method Cottections.enumeration yields an enumeration object that enumerates the elements in the collection. For example:

List<InputStream> streams = . . .;

var in = new SequenceInputStream(Cottections.enumeration(streams));

// the SequenceInputStream constructor expects an enumeration

3. Property Maps

A property map is a map structure of a special type. It has three particular characteristics:

  • The keys and values are strings.
  • The map can easily be saved to a file and loaded from a file.
  • There is a secondary table for default values.

The Java platform class that implements a property map is called Properties. Property maps are useful in specifying configuration options for programs. For example:

var settings = new Properties!);

settings.setProperty(“width”, “600.0”);

settings.setProperty(“filename”, “/home/cay/books/cj11/code/v1ch11/raven.html”);

Use the store method to save map list of properties to a file. Here, we just save the property map in the file program.properties. The second argument is a comment that is included in the file.

var out = new FileOutputStream(“program.properties”);

settings.store(out, “Program Properties”);

The sample set gives the following output:

#Program Properties

#Sun Dec 31 12:54:19 PST 2017

top=227.0

left=1286.0

width=423.0

height=547.0

filename=/home/cay/books/cj11/code/v1ch11/raven.html

To load the properties from a file, use

var in = new FileInputStream(“program.properties”);

settings.load(in);

The System.getProperties method yields a Properties object to describe system in­formation. For example, the home directory has the key “user.home”. You can read it with the getProperties method that yields the key as a string:

String userDir = System.getProperty(“user.home”);

To get the Java version of the virtual machine, look up the “java.version” property. You get a string such as “11.0.1” (or “1.8.0” for Java 8.)

The Properties class has two mechanisms for providing defaults. First, whenever you look up the value of a string, you can specify a default that should be used automatically when the key is not present.

String filename = settings.getProperty(“filename”, “”);

If there is a “filename” property in the property map, filename is set to that string. Otherwise, filename is set to the empty string.

If you find it too tedious to specify the default in every call to getProperty, you can pack all the defaults into a secondary property map and supply that map in the constructor of your primary property map.

var defaultSettings = new Properties();

defaultSettings.setProperty(“width”, “600”);

defaultSettings.setProperty(“height”, “400”);

defaultSettings.setProperty(“filename”, “”);

var settings = new Properties(defaultSettings);

Yes, you can even specify defaults to defaults if you give another property map parameter to the defaultSettings constructor, but it is not something one would normally do.

The companion code has a sample program that shows how you can use properties for storing and loading program state. The program uses the ImageViewer program from Chapter 2 and remembers the frame position, size, and last loaded file. Run the program, load a file, and move and resize the window. Then close the program and reopen it to see that it remembers your file and your favorite window placement. You can also manually edit the file .corejava/ImageViewer.properties in your home directory.

Properties are simple tables without a hierarchical structure. It is common to introduce a fake hierarchy with key names such as window.main.color, window.main.titte, and so on. But the Properties class has no methods that help organize such a hierarchy. If you store complex configuration information, you should use the Preferences class instead—see Chapter 10.

4. Stacks

Since version 1.0, the standard library had a Stack class with the familiar push and pop methods. However, the Stack class extends the Vector class, which is not satisfactory from a theoretical perspective—you can apply such un-stack­like operations as insert and remove to insert and remove values anywhere, not just at the top of the stack.

5. Bit Sets

The Java platform’s BitSet class stores a sequence of bits. (It is not a set in the mathematical sense—bit vector or bit array would have been more appropriate terms.) Use a bit set if you need to store a sequence of bits (for example, flags) efficiently. A bit set packs the bits into bytes, so it is far more efficient to use a bit set than an ArrayList of Boolean objects.

The BitSet class gives you a convenient interface for reading, setting, and re­setting individual bits. Using this interface avoids the masking and other bit­fiddling operations that are necessary if you store bits in int or long variables.

For example, for a BitSet named bucketOfBits,

bucketOfBits.get(i)

returns true if the ith bit is on, and false otherwise. Similarly,

bucketOfBits.set(i)

turns the ith bit on. Finally,

bucketOfBits.clear(i)

turns the ith bit off.

As an example of using bit sets, we want to show you an implementation of the “sieve of Eratosthenes” algorithm for finding prime numbers. (A prime number is a number like 2, 3, or 5 that is divisible only by itself and 1, and the sieve of Eratosthenes was one of the first methods discovered to enumerate these fundamental building blocks.) This isn’t a terribly good algorithm for finding the primes, but for some reason it has become a popular benchmark for compiler performance. (It isn’t a good benchmark either, because it mainly tests bit operations.)

Oh well, we bow to tradition and present an implementation. This program counts all prime numbers between 2 and 2,000,000. (There are 148,933 primes in this interval, so you probably don’t want to print them all out.)

Without going into too many details of this program, the idea is to march through a bit set with 2 million bits. First, we turn on all the bits. After that, we turn off the bits that are multiples of numbers known to be prime. The positions of the bits that remain after this process are themselves prime numbers. Listing 9.8 lists this program in the Java programming language, and Listing 9.9 is the C++ code.

   

Source: Horstmann Cay S. (2019), Core Java. Volume I – Fundamentals, Pearson; 11th edition.

Leave a Reply

Your email address will not be published. Required fields are marked *