Managing Object Collections


In the Java library, a number of classes ”such as HashMap and Vector ”support collections. Although these classes do their job, they are meant to be generic and solve a wide range of problems. The Java library, on the other hand, is meant to be generic. The Jakarta Commons project Collections contains all of the specialized collection classes.

The Collections package contains many classes. The book will not outline every collection class in detail because that would simply take up too much space. Instead, we explain the main concepts and provide some examples. We will not discuss the remaining classes; it's up to you to investigate those.

Technical Details for the Collections Package

Tables 7.3 contains the abbreviated details necessary to use the Collections package.

Table 7.3: Repository details for the Collections package.

Item

Details

CVS repository

jakarta-commons

Directory within repository

Collections

Main packages used

org.apache.commons.collections (all packages below this package are detailed implementations that support the classes in the higher-level package)

Defining the Basis

All collections in the Collections package are based on some interface defined in the Collections package. The Collections package contains the following interfaces:

  • Bag : This is a collection that can have multiple instances of the same item in the collection.

  • BoundedCollection : This is a collection that has a maximum number of elements that can be managed.

  • Buffer : This collection is not just a random collection of objects, but a collection where the order of the objects is very important. For example, a Stack requires that you respect the order in which the objects were added.

  • Closure : This represents a interface implementation that has an associated action. This is similar to the Command best practice, which we discussed in Chapter 6.

  • Factory : This represents an interface used to instantiate a specific class. This is used by some collections and was explained in Chapter 3.

  • MultiMap : This represents an interface that is similar to a Java library-defined Map . The difference is the interface MultiMap can associate multiple items with a single key.

  • Predicate : This represents an interface implementation that is used to test an object resulting in either a true or false value.

  • PriorityQueue : This defines an interface that represents a heap where there are no maximum or minimum values.

  • SortedBag : This is an interface similar to the interface Bag , except that the Bag implementation can be sorted.

  • Transformer : This represents an interface implementation that performs a cloned conversion of a specific object.

Don't worry if the purpose of the various interfaces is still a puzzle. At this point, we're just introducing you to the interfaces and are highlighting the fact that there are two types of interfaces in the Collections package. The first type of interface is a typical collection interface like Bag . This type of interface manages actual collections of objects. The second type of interface is an action interface. This type of interfaces performs generic operations on a collection. For example, a collection filtering action would implement the interface Closure .

Using Bag

One of the simplest classes to use is HashBag , which implements the interfaces Bag and Collection . The implementation of the interface Collection highlights the fact that most collection classes implement the interface Collection . Listing 7.30 shows a simple usage of the class HashBag .

Listing 7.30
start example
 Bag bag = new HashBag(); BeanToWrite bean1 = new BeanToWrite( 1234, "hello"); BeanToWrite bean2 = new BeanToWrite( 2345, "goodbye"); bag.add( bean1); bag.add( bean2, 2); Iterator iter = bag.iterator(); while( iter.hasNext() == true) { BeanToWrite bean = (BeanToWrite)iter.next(); System.out.println( bean.toString()); } 
end example
 

In Listing 7.30, the variable bag is an instantiated HashBag . To add elements to the HashBag class, you use the method add . In the second example of using the method add , there is a second parameter. The second parameter is a number that defines how many references of the object will be added to the Bag . In Listing 7.30, the number 2 means that two references of the object will be added to the Bag . To iterate the bag, an Iterator class instance is retrieved using the method iterator .

You can count individual object elements using the method getCount , as shown in Listing 7.31.

Listing 7.31
start example
 System.out.println( "bean1 count: " + bag.getCount( bean1)); System.out.println( "bean2 count: " + bag.getCount(bean2)); System.out.println( "total count: " + bag.size()); 
end example
 

The method getCount has one parameter, which is the bean instance that is to be counted in the bag. To find the total number of elements the method, size is used.

To remove elements from the bag, the method remove is used. This is illustrated in Listing 7.32.

Listing 7.32
start example
 bag.remove( bean1); bag.remove( bean2, 1); 
end example
 

In Listing 7.32, the method remove is used in two different variations. In the first variation, the method remove removes all instances of the object from the bag. In the second variation, the method remove removes the number of instances, as per the second parameter, of the object from the bag. After Listing 7.30 and 7.32 have executed, only one element would be left in the bag.

Finally, the methods removeAll , retainAll , and containsAll expect a collection, which the bag then acts upon. The problem with these methods is that they are currently in violation of how a bag should operate . The violations involve implementing the method incorrectly. When the methods implement the functionality correctly, all backward compatibility will be broken. We won't demonstrate the code here. If you want to see if the methods have been updated, we suggest that you read the Java Docs.

Finally, the method uniqueSet is used to generate a unique collection of objects where there are no doubles. An example of using uniqueSet is Listing 7.33.

Listing 7.33
start example
 java.util.Set set = bag.uniqueSet(); 
end example
 

Using SortedBag

Another variation of the Bag is the SortedBag . The purpose of the SortedBag is to organize data in a collection according to some sorting algorithm. An implementation of the interface SortedBag is the class TreeBag . You can just use the TreeBag like the HashBag without any additional work. However, that would be incorrect since the sorting algorithm may not be correct. Ideally, you should develop a comparator and associate it with the SortedBag . An example of the sorting algorithm is Listing 7.34.

Listing 7.34
start example
 class BeanToWriteComparator implements java.util.Comparator { public int compare(Object o1, Object o2) { BeanToWrite bean1 = (BeanToWrite)o1; BeanToWrite bean2 = (BeanToWrite)o2; if( bean1.getIntegerValue() <  bean2.getIntegerValue()) { return -1; } else if( bean1.getIntegerValue() > bean2.getIntegerValue()) { return 1; } else { return 0; } } } 
end example
 

In Listing 7.34, the class BeanToWriteComparator implements the interface Comparator . The interface Comparator has two methods, but the only method you're required to implement is compare . The purpose of the method compare is to compare two objects against each other and check if one object is less than, greater than, or equal to the other object. The exact reason why an object is greater than another object depends on the implementation of the method. In Listing 7.34, a comparison means casting the individual objects to the class BeanToWrite and then comparing the integerValue properties. Three possible values can be returned. A negative integer is returned if the object o1 is less than the object o2 . A positive integer is returned if the object o1 is greater than o2 . A zero is returned if object o1 equals object o2 .

Once you've created the comparator, the TreeBag can use it, as shown in Listing 7.35.

Listing 7.35
start example
 SortedBag bag = new TreeBag( new BeanToWriteComparator()); bag.add( new BeanToWrite( 3456, "hello")); bag.add( new BeanToWrite( 2345, "goodbye"), 2); Iterator iter = bag.iterator(); while( iter.hasNext() == true) { BeanToWrite bean = (BeanToWrite)iter.next(); System.out.println( bean.toString()); } 
end example
 

In Listing 7.35, the class TreeBag is instantiated where the constructor has the additional parameter of the comparator instance. The comparator needs to be associated when the bag is instantiated because otherwise you can't associate a comparator. Once the bag has been instantiated, you can retrieve the comparator using the method comparator. At that point, you could change some parameters to alter the sorting algorithm. However, that is not a good idea because there is no method to re- sort the objects in the bag afterwards.

Once a TreeBag class instance is available, you can add elements and iterate through the objects like in Listing 7.30. The difference with the iteration in Listing 7.35 is that the individual objects will be sorted in increasing integerValue property values, instead of being in the order they were in when they were added to the TreeBag class.

Using Decorators on a Bag Collection

When you first hear the term decorator , a cake decorator may come to mind, but of course, that's not what we're talking about. The problem with the term decorator is that the Java Docs don't explicitly explain what a decorator does. Essentially , the Java Docs say that the decorators decorate another collection. To understand a decorator, we need to look at the Decorator pattern.

The purpose of the Decorator pattern is to add additional functionality to an object without actually changing the code of the object. Think of a decorator as a team of objects that all look the same except that each helps the other solve a bigger problem.

In this context, the decorator is understandable and refers to the definition of a set of classes that augment the capabilities of the individual collection type. In the case of the interface Bag, the class BagUtils is used to create decorators. An example of using the class BagUtils is Listing 7.36.

Listing 7.36
start example
 HashBag bag = new HashBag(); bag.add( new BeanToWrite( 1234, "hello")); bag.add( new BeanToWrite( 2345, "goodbye"), 2); Bag syncBag = BagUtils.synchronizedBag( bag); 
end example
 

In Listing 7.36, it is still necessary to allocate a base collection like we did in previous listings. The decorator is added by calling the static method synchronizedBag . The method synchronizedBag instantiates the class SynchronizedBag , which exposes a set of synchronized methods. The synchronized methods operate individually on the HashBag instance allocated at the beginning of the Listing 7.36. The decorator used in Listing 7.36 adds synchronization capabilities to the class HashMap .

The following sections outline all of the available decorator methods. When the decorator identifier includes the word "sorted," then that decorator works like the decorator identifier without the sorted identifier. The decorated identifier with the "sorted" word includes the sorting capabilities. Decorators can be applied multiple times. For example, if you go back to Listing 7.36, the variable syncBag can be used as an input parameter for another decorator.

predicatedBag, predicatedSortedBag

The decorator methods predicatedBag and predicatedSortedBag are used to manage a predicated collection. A predicated collection is a collection that performs some type of validation before the item is added to the collection. The validation will either accept or reject the object to be added. Listing 7.37 is a sample predicate implementation.

Listing 7.37
start example
 class BeanToWritePredicate implements Predicate { public boolean evaluate(Object object) { BeanToWrite bean = (BeanToWrite)object; if( bean.getIntegerValue() != 1234) { return false; } return true; } } 
end example
 

In Listing 7.37, the class BeanToWritePredicate implements the interface Predicate , which requires the implementation of the method evaluate . The method evaluate has one parameter, which is the object to be validated. If the object is validated and acceptable, then the method evaluate returns a value of true . Otherwise, a value of false is returned. Listing 7.38 shows how to use the predicate class.

Listing 7.38
start example
 Bag bag = new HashBag(); Bag decoratedBag = BagUtils.predicatedBag( bag, new BeanToWritePredicate()); decoratedBag.add( new BeanToWrite( 1234, "hello")); try { decoratedBag.add( new BeanToWrite( 2345, "goodbye"), 2); } catch( IllegalArgumentException ex) { System.out.println( "oops could not add to bag"); } 
end example
 

In Listing 7.38, the first step is to instantiate the underlying bag implementation. The predicated bag collection is instantiated using the method predicatedBag . The second parameter of the method predicatedBag is the class that implements the interface Predicate. Then, whenever a consumer uses the method add of the decorated bag, the class method BeanToWrite.evaluate is called. In the example of the second add method call, a failure will occur because the property integerValue is not 1234 , as required by Listing 7.37. This means the validation will fail and an IllegalArgumentException error will be generated. Therefore, to let the application continue, the second add method call is wrapped in an exception block to catch the exception.

synchronizedBag, synchronizedSortedBag

The methods synchronizedBag and synchronizedSortedBag have already been illustrated and are shown in Listing 7.36. If you missed them, the purpose of synchronizedBag and synchronizedSortedBag is to be able synchronize access to the data members , which is very useful in a multi-threaded scenario.

transformedBag, transformedSortedBag

The method transformedBag is similar to the method predicatedBag in that another class has to be defined. The classes transformedBag and transformedSortedBag are operation classes that clone and manipulate the object to be added. The full intention of the interface Transformer is that an object is transformed from one object into another and that the original object is left untouched. Listing 7.39 is a sample class that implements a transformation.

Listing 7.39
start example
 class BeanToWriteTransformed implements Transformer { public Object transform( Object value) { try { return BeanUtils.cloneBean( value); } catch( Exception ex) { return null; } } } 
end example
 

In Listing 7.39, the class BeanToWriteTransformed implements the interface Transformer. The interface Transformer has only one method to implement, transform . The purpose of the method transform is to create a new object based on the original input object represented by the parameter value . In Listing 7.39, there is no transformation, just a cloning of the object using the class method BeanUtils.cloneBean . This is the absolute minimum that should occur. If there are any errors to report, exceptions should be generated. The following exceptions can be generated:

  • ClassCastException : This means that the input class was not of the correct type.

  • IllegalArgumentException : This means that the input class is not in a correct state or valid.

  • FunctorException : This means that the class could not be transformed.

Once the transformer class has been implemented, it can be used like in Listing 7.40.

Listing 7.40
start example
 Bag bag = new HashBag(); Bag decoratedBag = BagUtils.transformedBag( bag, new BeanToWriteTransformed()); decoratedBag.add( new BeanToWrite( 1234, "hello")); 
end example
 

Listing 7.40 is virtually identical to Listing 7.38. The difference is that the method transformedBag is used here.

When implementing the interface Transformer , the method transform could have returned the original object. However, that would be violating the contract. There is nothing in the sources to prevent the implementation from returning the original. It is important that the contract be kept up, because further down in the program, there might be a piece of code that manipulates the objects in the bag. That would cause the original objects to be altered .

typedBag, typedSortedBag

The methods typedBag and typedSortedBag are used to create a typed bag, which is a bag that allows only specific types of objects in the bag. Listing 7.41 is an example of how to use a typed bag.

Listing 7.41
start example
 Bag bag = new HashBag(); Bag decoratedBag = BagUtils.typedBag( bag, BeanToWrite.class); decoratedBag.add( new BeanToWrite( 1234, "hello")); try { decoratedBag.add( new BetwixtBean( 2345, "goodbye")); } catch( IllegalArgumentException ex) { System.out.println( "oops cannot add"); } 
end example
 

In Listing 7.41, the method typedBag has two parameters. The first parameter is the bag to decorate. The second parameter is a class descriptor that specifies the class that can be added to the bag. The first instance of the method add is acceptable because the class that is added is the right class type. The second instance of the method add attempts to add the class type BetwixtBean . The second instance of the method does not succeed because it is the incorrect type and generates an IllegalArgumentException .

unmodifiableBag, unmodifiableSortedBag

The methods unmodifiableBag and unmodifiableSortedBag return a bag that cannot be modified. The bag essentially becomes readonly . Any attempt to modify the bag will result in an UnsupportedOperationException exception.

More Default Predicates

In Listing 7.37, we created a user -defined predicate. Within the Collections package are a number of default predicates that you can use to carry out default actions. Alternatively, you can combine predicates to create another predicate, as shown in Listing 7.42.

Listing 7.42
start example
 class AndPredicate implements Predicate { private Predicate _p1, _p2; public AndPredicate( Predicate p1, Predicate p2) { _p1 = p1; _p2 = p2; } public boolean evaluate(Object object) { if( _p1.evaluate(object) && _p2.evaluate(object)) { return true; } else { return false; } } } 
end example
 

In Listing 7.42, the class AndPredicate , which requires two other predicates when instantiated, is created. In the implementation of the method evaluate, the evaluation is handed off to the other two predicates. In addition, if the other two predicates return a value of true , then the class AndPredicate returns true as well. Notice in Listing 7.42 that predicates can be decorated like collections. The class PredicateUtils is a wrapper that can create many types of predicates that could be useful to a programmer. We will now discuss the predicate methods that the class PredicateUtils exposes to create specific predicate instances.

Note

Note that each method returns a decorated predicate, which will be called "decorated predicate" for the scope of the following list. When the word "predicate/s" is used, it refers to the predicate/s passed in as a parameter/s to the method. When the word "predicate method" is used, it refers to the method that the class PredicateUtils exposes. Finally, when the term "input object" or "added object" is used, it refers to an object being added to a collection, like in Listing 7.41.

Some of the purposes of the predicates may seem a bit puzzling; for example, the predicate method nullIsTruePredicate seems unnecessary. In fact, the idea is not to use this predicate method on its own. The idea is to use this decorated predicate in conjunction with another decorated predicate. For example, imagine when a null object is passed in; generally , you would not want to generate an exception. Using the predicate method nullIsTruePredicate , an exception would not be generated. The idea is to chain decorated predicates together to follow a defined ruleset.

invokerPredicate

There are two variations to this method. The first variation has one parameter, which is a method name. The second variation has three parameters. The first parameter is the method name , and the second parameter is an array of class descriptors that describe the third parameter, which is an array of objects.

The idea of this decorated predicate is that whenever you attempt to add an object to the collection, a method is called. The method is the name of the method passed to the predicate method. An example of a bean that supports an invoked method is Listing 7.43.

Listing 7.43
start example
 public class CollectionBean { private int _value; public CollectionBean( int value) { _value = value; } public int getValue() { return _value; } public void setValue( int value) { _value = value; } public boolean validate() { return true; } } 
end example
 

In Listing 7.43, the class CollectionBean exposes the method validate . The method that is called must return either a boolean primitive or a Boolean class instance without any parameters. If the method returns false , then an IllegalArgumentException is generated. Listing 7.44 shows how to use the invokerPredicate .

Listing 7.44
start example
 Bag bag = new HashBag(); Bag decoratedBag = BagUtils.predicatedBag( bag, PredicateUtils.invokerPredicate( "validate")); decoratedBag.add( new CollectionBean( 1234)); 
end example
 

In Listing 7.44, the class PredicateUtils.invokerPredicate is used to create the instantiated predicate. Then, when the method add is called, the predicate will invoke the validate method of the class CollectionBean defined in Listing 7.43. In Listing 7.44, the usage of the PredicateUtils class is a prototype for all the predicate methods that are going to be defined.

allPredicate

There are two variations to this predicate method. The first variation accepts a Collection class instance, and the second variation accepts an array of Predicate implementations. The decorated predicate returns a true value if all of the results in from the contained predicate collection return a true value.

andPredicate

This predicate method has two parameters, which are two predicate instances. The decorated predicate returns true if the two predicate instances return true. The decorated predicate is identical in functionality to Listing 7.43.

anyPredicate

There are two variations to this predicate method. The first variation accepts a Collection class instance, and the second variation accepts an array of Predicate implementations. The result is a decorated predicate that returns true if any of the predicates in the collection return true .

asPredicate

This predicate method takes as a parameter a Transformer instance implementation. This predicate method is a bit odd because whenever an object is added, that object is passed to the Transformer instance. It is expected that the Transformer instance accepts the input object and then returns a Boolean instance.

eitherPredicate

Like the andPredicate predicate method, the eitherPredicate predicate method accepts two parameters, which are predicates. If either of the two predicates return true, then the decorated predicate returns true. However, if both predicates return true, then a false is returned by the decorated predicate.

equalPredicate

This predicate method takes one parameter, which is a reference object. The decorated predicate will compare every object and check if it is equal to the reference object by calling the identity object's equals method.

exceptionPredicate

This predicate method generates an exception whenever the decorated predicate is called.

falsePredicate

This predicate method returns an instantiated predicate that returns a value of true every time.

identityPredicate

This predicate method takes one parameter, which is an identity object. The returned predicate instance will check every input object to see if it is the same object. What this predicate essentially does is manage a collection where only one object can be added.

instanceofPredicate

This predicate method takes one parameter, which is a class descriptor. The decorated predicate uses the class descriptor to check if every added object is of a specific type. In a sense, this is like using the typed bag decorator described earlier in this chapter.

neitherPredicate

This predicate method accepts two predicates as parameters. The decorated predicate returns true if neither of the predicates returns true .

nonePredicate

There are two variations to this predicate method. One variation is where the predicate method accepts a single parameter of the class type Collection . The second variation is where the predicate method accepts a single parameter of an array of class type Predicate . For either variation, the decorated predicate returns true if none of the predicates returns true .

notNullPredicate

This predicate function has no parameters. It's a bit peculiar in that the decorated predicate returns true if the object to be added is not null.

notPredicate

This predicate function has one parameter, which is a predicate. The decorated predicate inverts the answer the predicate returns. Therefore, if the predicate returns true, then the decorated predicate returns false .

nullIsExceptionPredicate

The decorated predicate throws an exception if the input object is null.

nullIsFalsePredicate

The decorated predicate returns false if the input object is null.

nullIsTruePredicate

The decorated predicate returns true if the input object is null.

nullPredicate

The decorated predicate returns true if the input object is true .

onePredicate

This predicate method has one parameter, which is an array of predicates. The decorated predicate returns true if any of the predicates returns true .

orPredicate

This predicate method has two parameters, which are predicates. If any or both predicates return true, then the decorated predicated returns true .

truePredicate

This predicate method has no parameters. The decorated predicate always returns true.

uniquePredicate

This predicate method has no parameters. The decorated predicate always returns true the first time an instance of an object is passed in. If the same object instance is passed in again, then a false is returned by the decorated predicate.

More Default Transformers

As with the class PredicateUtils , you can chain Transformer s together. The class TransformerUtils exposes a number of methods that provide default Transformer interface-based functionality. We will now discuss the transformer methods that the class TransformerUtils exposes.

Note

Note that each method returns a decorated transformer, which will be called "decorated transformer" for the scope of the following list. When the word "transformer(s)" is used, it refers to the transformer(s) passed in as a parameter(s) to the method. When the word "transformer method" is used, it refers to the method that the class TransformerUtils exposes. Finally, when the term "input object" or "added object" is used, it refers to an object being added to a collection, like in Listing 7.41.

asTransformer

There are three variations to this transformer method. The first variation has a single parameter, which is an implementation of the interface Closure . The interface closure is like the Command best practice. It is expected that the Closure implementation perform a transformation implementation. The second variation has a single parameter, which is an implementation of the interface Factory . The odd part of this implementation is that the factory does not process the input object, and the input object could be gibberish. The last variation has a single parameter and is an implementation of the Predicate interface.

chainedTransformer

There are three variations to this transformer method. In all three variations, the decorated transformer manages a Collection of transformers, arrays, or two transformers. Whenever the decorated transformer is called, the transformers are called, and the input to the transformer is the output of a previous transformer.

cloneTransformer

Here, the decorated transformer clones the input object, which is identical in behavior to Listing 7.39.

constantTransformer

This transformer method has one parameter, which is an input object. Each time the decorated transformer is called, the input object is returned.

exceptionTransformer

When this decorated transformer is called, an exception is generated.

instantiateTransformer

This transformer method has two variations. In the first variation, there are no parameters. With this decorated transformer, it is expected that the input object be a class descriptor that will be instantiated to produce an object. In a sense, this variation works in a similar fashion as a factory, except that the class descriptor is passed to the decorated transformer. In the second variation, there are two parameters. The first parameter is an array of class descriptors. The second parameter is an array of objects. These two parameters are used as constructor parameters for the input object.

invokerTransformer

This transformer method has two variations. In the first variation, there is only one parameter, which is a string name for a method. In the second variation, there are three parameters. The first parameter is a string that defines a method name. The second parameter is an array of class descriptors. The third parameter is an array of objects. In any of these variations, the name of the method represents a method that is invoked on the input object. In the second variation, the second and third parameters identify parameters on the invoked method.

mapTransformer

This transformer method has one parameter, which is a Map interface implementation instance. The idea behind this transformer is that the input object represents a key in the Map , which is associated with a value. The associated value is then returned as a transformed object by the decorated transformer.

nopTransformer

This decorated transformer returns the input object as a transformed object. You should generally not use this transformer implementation since it does not exactly adhere to the specification of the Transformer interface.

nullTransformer

This decorated transformer does nothing and always returns null.

switchMapTransformer

This transformer method has one parameter, which is a Map interface implementation instance. The Map contains a number of key value pairs, where the key is an object and the value a transformer. When called with an input object, the decorated transformer will use the input object to cross reference to a key. The key references a transformer, which will be called, and the input object will be the parameter.

switchTransformer

This transformer method has various implementations, but in every implementation there are two common parameters: a collection of predicates and a matching number of transformers. When the decorated transformer is called, the input object is called for all of the predicates. For those predicates that returned a value of true , the predicate associated transformer is called with the input object. This transformer method is very versatile because it enables you to create a ruleset using predicates, which then activate transformers.

More Default Closures

The last utility class that we will cover is the interface Closure, which we have mentioned a few times. Listing 7.45 defines the Closure interface.

Listing 7.45
start example
 public interface Closure { public void execute(Object input); } 
end example
 

In Listing 7.45, the interface Closure has only one method, execute . The method execute has only one parameter, which represents some type of object. You can define custom Closure implementations, but the class ClosureUtils has already implemented some default functionalities, which we will now discuss.

Note

Note that each method returns a decorated closure, which will be called "decorated closure" for the scope of the following list. When the word "closure(s)" is used, it refers to the closure(s) passed in as a parameter(s) to the method. When the word "closure method" is used, it refers to the method that the class ClosureUtil exposes. Finally, when the term "input object" or "added object" is used, it refers to an object being added to a collection, as in Listing 7.41.

asClosure

This closure method has one parameter, which is a Transformer implementation. The decorated closure will call the Transformer implementation. The problem, though, is that a Transformer is supposed to operate on a input object and not touch it; however, the Closure interface does not return a value. The result is that the Transformer creates a new object that is not saved anywhere .

chainedClosure

This closure method has three similar variations. The idea is to combine a number of Closure implementations into a collection, which are all called when the decorated closure is called.

doWhileClosure

This closure method has two parameters. The first parameter is the closure itself, and the second parameter is a predicate. When the decorator closure is called, the closure is called first. Then, the predicate is called; if the predicate returns true , the closure is called again. This process repeats until the predicate returns a value of false . The input object is passed to both the closure and the predicate and is considered the shared object.

exceptionClosure

This decorated closure generates an exception every time.

forClosure

This closure method has two parameters. The first parameter is a count, and the second parameter a closure. The decorated closure then loops , and for each loop calls the closure until the count has been reached.

invokerClosure

This closure method has two variations. In the first variation, there is only one parameter, which is a string name for a method. In the second variation, there are three parameters. The first parameter is a string that defines a method name. The second parameter is an array of class descriptors. The third parameter is an array of objects. In any of these variations, the name of the method represents a method that is invoked on the input object. In the second variation, the second and third parameters identify parameters on the invoked method.

nopClosure

The decorated closure does nothing and is meant as an empty placeholder that could be used a default value.

switchClosure

This closure method has various implementations, but in every implementation there are two common parameters: a collection of predicates and a matching number of transformers. When the decorated closure is called, the input object is called for all of the predicates. For those predicates that returned a value of true , the associated closure is called. This closure method is very versatile because it enables you to create a ruleset using predicates, which then activate transformers.

switchMapClosure

This closure method has one parameter, which is a Map interface implementation instance. The Map contains a number of key value pairs, where the key is an object and the value a closure. When called with an input object, the decorated closure will use the input object to cross-reference to a key. The key references a closure, which will be called, and the input object will be the parameter.

whileClosure

This closure method has two parameters. The first parameter is the predicate itself, and the second parameter is a closure. When the decorator closure is called, the predicate is called, which starts a loop. The loop continues if the predicate returns a value of true . Otherwise, the loop is stopped and the decorated closure is exited. If the loop continues, the closure is executed and the loop starts again.

Using Fast Lists and Maps

Writing multi-threading code can sometimes be frustrating; what would seem to make an application faster either slows the applications down or brings it to a halt. Multi-threaded code needs to have synchronization at certain places in the program. We discussed all of these issues in Chapter 4.

Within the Collections package are the classes FastArrayList , FastHashMap , and FastTreeMap . These classes were programmed for a multi-threaded environment. Listing 7.46 shows how to use the class FastArrayList .

Listing 7.46
start example
 FastArrayList list = new FastArrayList(); list.add( new BetwixtBean( 1234, "hello")); list.setFast( true); list.add( new BetwixtBean( 2345, "goodbye")); 
end example
 

The class FastArrayList in Listing 7.46 is similar to all of the collection classes that have been shown thus far. However, there is a very big implementation difference, which is activated by the method setFast .

All of the fast collection classes are based on the premise that sometimes a collection is read and written, and sometimes a collection is read mostly. Read mostly is when the data in the collection is mainly queried and iterated, and not altered. You'll use different synchronization depending on which situation you're in. When an object is constantly modified using read and write operations, synchronization is the only option because the changes affect read operations. However, when an object is read mostly, synchronization is not as necessary.

We defined the concept of an immutable object in Chapter 4. An immutable object needs no synchronization, because once the object has been created it is not modified. The problem with an immutable object is that to make changes, you need to clone and then modify the immutable object. Immutable objects are not used in every multi-threaded situation because cloning can be expensive. Now, imagine if the object was read mostly; then, using the immutable concept would be very practical because for the few times the object is cloned, performance is not affected. This is the strategy used by the fast collection classes. An example of how this strategy is implemented is Listing 7.47.

Listing 7.47
start example
 public boolean add(Object element) { if (fast) { synchronized (this) { ArrayList temp = (ArrayList) list.clone(); boolean result = temp.add(element); list = temp; return (result); } } else { synchronized (list) { return (list.add(element)); } } } 
end example
 

In Listing 7.47, there is a decision block that decides whether or not the fast flag is set. The fast flag defines whether fast mode or slow mode is activated. Fast mode is when the collection is considered immutable, and slow mode is when the object uses synchronization. When the fast flag has a value of true , then fast mode is activated. In slow mode, the lower part of the decision block is executed. In the lower part of the decision block, a synchronization is executed on the list object. This means that when an element is added, a synchronization is executed, which will stop any type of operation whether it be read or write.

Let's go back to the upper part of the decision block in Listing 7.47. Here, the synchronization is only on the this object. While this would seem like synchronization based on the slow mode, there is a big difference. In slow mode, the synchronization is on the list itself, which means any references to the list result in a block. However, in fast mode, there is no synchronization. The synchronization on the this object is necessary because once the list has been cloned, the old list reference has to be replaced with the new one. Doing this would require synchronization so that only one thread can modify the list variable.

The fast collection classes are defined as follows :

  • FastArrayList : This is an implementation of the class java.util.ArrayList .

  • FastHashMap : This is an implementation of the class java.util.HashMap .

  • FastTreeMap : This is an implementation of the class java.util.TreeMap .

There is a catch to the fast collections approach, however. These classes may cause unexpected errors on specific platforms. The cause of the errors is a common problem that depends on the optimization in performance on some platforms. For more details, see www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html .

Other Classes and the Details

We'll now move on to discussing some other classes that are available in the Collections package.

Tip

We recommend that you do a quick read of these classes. These classes are extensions of the concepts we've already covered, so we don't allocate that much space to them. However, we still want you to know how to use these classes appropriately.

  • ArrayStack : This class implements a stack. This means that the last added item to the list is also the first item removed. The class ArrayStack is based on the class java.util.ArrayList . When iterating using an Iterator, the iterator starts at the bottom of the stack.

  • BeanMap : This class implements the interface java.util.Map . The main purpose of this map is to be able to manipulate and iterate a Java Bean using a collection mech-anism. The Java Bean is set and retrieved using the methods setBean and getBean , respectively.

  • BinaryHeap : This is a binary implementation of the interface PriorityQueue and Buffer . Simply put, the class BinaryHeap is an implementation of a stack like the class ArrayStack . The difference, though, is that the addition and removal order is determined by an ordering. The ordering is determined by the Comparator interface implementation.

  • BoundedFifoBuffer : This is an efficient implementation of a buffer that always maintains a constant buffer size. The first element added is always the first element removed from the list (Fifo = first in first out). Iterating the elements occurs in the same order.

  • CircularFifoBuffer : This is fixed length buffer that can always be written to. As with the class BoundFifoBuffer , if the buffer becomes full, the oldest element is overridden. This class is extremely useful in live-feed-type situations, where old data is not useful. With this class, a window of data across time is typically stored.

  • CursorableLinkedList : This is a double linked list that implements the List interface.

  • ExtendedProperties : This is an implementation of the class java.util.HashMap . If an element already exists in the HashMap , then the already existing contents are concatenated with the new content. Usually, the HashMap implementation simply overwrites the old data.

  • LURMap : This is an implementation of the Map interface. The class has a maximum number of elements. When the limited is reached, an addition of a new element will remove the least recently used map entry.

  • NodeCachingLinkedList : This is a double linked list implementation that caches the added and deleted items. This class is very useful in situations where a list is constantly reorganized by the addition, removal, or reordering of the same elements. Otherwise, this class can entail using a large amount of resources.

  • ReferenceMap : This is a reference map that allows the use of soft references that can be garbage collected by the JVM.

  • SequencedHashMap : This is a class where the addition of the elements produces an ordered sequence of items added list.

  • StaticBucketMap : This is an implementation of the interface Map , which can be used very efficiently in multi-threaded environments.

  • SynchronizedPriorityQueue : This is a thread safe implementation of the interface PriorityQueue .

  • UnboundedFifoBuffer : This very efficient implementation of the Buffer interface can be used instead of the class ArrayList .




Applied Software Engineering Using Apache Jakarta Commons
Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
ISBN: 1584502460
EAN: 2147483647
Year: 2002
Pages: 109

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