You need to iterate through elements of a Collection
that match a specified condition.
Or, you have a Collection
from which
you need to remove elements not satisfying a condition.
Create a FilterIterator
with a Predicate
; if
the Predicate
returns true
for an element, that element will be
included in the Iterator
. The
FilterIterator
decorates another
Iterator
and provides the ability to
apply an arbitrary filter to a Collection
. In the following example, EarthQuake
beans are kept in an ArrayList
that is filtered using the majorQuakePredicate
and a FilterIterator
:
import org.apache.commons.collection.Predicate; import org.apache.commons.collection.iterators.FilterIterator; List quakes = new ArrayList( ); EarthQuake quake1 = new EarthQuake( ); quake1.setLocation( "Chicago, IL" ); quake1.setIntensity( new Float( 6.4f ) ); quake1.setIntensity( new Float( 634.23f ) ); quake1.setTime( new Date( ) ); quakes.add( quake1 ); EarthQuake quake2 = new EarthQuake( ); quake2.setLocation( "San Francisco, CA" ); quake2.setIntensity( new Float( 4.4f ) ); quake2.setIntensity( new Float( 63.23f ) ); quake2.setTime( new Date( ) ); quakes.add( quake2 ); Predicate majorQuakePredicate = new MajorQuakePredicate( new Float(5.0), new Float(1000.0) ); Iterator majorQuakes = new FilterIterator( quakes.iterator( ), majorQuakePredicate ); while( majorQuakes.hasMore( ) ) { EarthQuake quake = (EarthQuake) majorQuakes.next( ); System.out.println( "ALERT! MAJOR QUAKE: " + quake.getLocation( ) + ": " + quake.getIntensity( ) ); }
An instance of MajorQuakePredicate
is created, and it is
passed to a FilterIterator
. Quakes
satisfying the criteria are returned by the FilterIterator
and printed to the
console:
ALERT! MAJOR QUAKE: Chicago, IL: 6.4
The Solution uses a custom Predicate
to select a subset of a Collection
, filtering EarthQuake
beans and alerting the user if a
major earthquake is measured. An earthquake is classified by intensity
on the Richter scale and the depth of the epicenter; this information is
modeled by the EarthQuake
bean defined in Example
5-1.
Example 5-1. An EarthQuake bean
package com.discursive.jccook.collections.predicates; public class EarthQuake { private String location; private Float intensity; private Float depth; private Date time; public class EarthQuake( ) {} public String getLocation( ) { return location; } public void setLocation(String location) { this.location = location; } public Float getIntensity( ) { return intensity; } public void setInsensity(Float intensity) { this.intensity = intensity; } public Float getDepth( ) { return depth; } public void setDepth(Float depth) { this.depth = depth; } public Date getTime( ) { return time; } public void setTime(Date time) { this.time = time; } }
An earthquake is considered major if it is above a five on the
Richter scale and above a depth of 1000 meters. To test each EarthQuake
object, a custom Predicate
, MajorQuakePredicate
, evaluates EarthQuake
objects, returning true
if an earthquake satisfies the criteria
for a major earthquake. The Predicate
defined in Example 5-2
encapsulates this decision logic.
Example 5-2. Major earthquake classification Predicate
package com.discursive.jccook.collections.predicates; import org.apache.commons.collections.Predicate; public class MajorQuakePredicate implements Predicate { private Float majorIntensity; private Float majorDepth; public MajorQuakePredicate(Float majorIntensity, Float majorDepth) { this.majorIntensity = majorIntensity; this.majorDepth = majorDepth; } public boolean evaluate(Object object) { private satisfies = false; if( object instanceof EarthQuake) { EarthQuake quake = (EarthQuake) object; if( quake.getIntensity( ).floatValue( ) > majorIntensity. floatValue( ) && quake.getDepth( ).floatValue( ) < majorDepth. floatValue( ) ) { satisfies = true; } } return satisfies; } }
If you want to create a Collection
of elements that match a Predicate
, you can remove elements from a
Collection
using CollectionUtils.filter( )
. CollectionUtils.filter( )
is destructive; it
removes elements from a Collection
.
The following example demonstrates the use CollectionUtils.filter()
to remove nonmatching
elements from a Collection
:
import org.apache.commons.collection.Predicate; import org.apache.commons.collection.iterators.FilterIterator; ArrayList quakes = createQuakes( ); Predicate majorQuakePredicate = new MajorQuakePredicate( new Float(5.0), new Float(1000.0) ); CollectionUtils.filter( quakes, majorQuakePredicate );
After the execution of this code, quakes
will only contain EarthQuake
objects that satisfy the MajorQuakePredicate
. If you don't want to alter or modify an existing
Collection
, use CollectionUtils.select()
or CollectionUtils.selectRejected()
to create a new Collection
with matching or nonmatching
elements. The following example demonstrates the use of CollectionUtils.select( )
and CollectionUtils.selectRejected( )
to select
elements from a Collection
leaving
the original Collection
unaffected:
import org.apache.commons.collection.Predicate; import org.apache.commons.collection.iterators.FilterIterator; ArrayList quakes = createQuakes( ); Predicate majorQuakePredicate = new MajorQuakePredicate( new Float(5.0), new Float(1000.0) ); Collection majorQuakes = CollectionUtils.select( quakes, majorQuakePredicate ); Collection minorQuakes = CollectionUtils.selectRejected( quakes, majorQuakePredicate );
The majorQuakes
Collection
contains EarthQuake
objects satisfying the majorQuakePredicate
, and the minorQuakes
Collection
contains EarthQuake
objects not satisfying the majorQuakePredicate
. The quakes List
is not modified by select( )
or selectRejected( )
.
Collections can be filtered via a combination of CollectionUtils
and Predicate
objects, or you can also select
elements from a Collection
using an
XPath expression. Recipe 12.1
demonstrates the use on Commons JXPath to query a Collection
.