In this blog entry, I'll try to explain another functional programming feature introduced by Google Guava library: Predicates. They work like the Function class with the difference that they just return a boolean value. It's possible to use Predicates for filtering elements in an Iterable that satisfy the given Predicate or to check if all/any element in an Iterable satisfies the given Predicate.
Lets write a Predicate that accepts Person objects with names that began with a vowel letter. But first, let me show you what's Person object.
01 | class Person implements Comparable < Person > { |
02 | public Person(String name, String lastName){ |
04 | this .lastName = lastName; |
09 | public String getName() { |
12 | public void setName(String name) { |
15 | public String getLastName() { |
18 | public void setLastName(String lastName) { |
19 | this .lastName = lastName; |
22 | private String lastName; |
24 | public int compareTo(Person arg0) { |
25 | return this .getName().compareTo(arg0.getName()); |
29 | public String toString() { |
30 | return this .name+ " " + this .lastName; |
Now lets see what a Predicate looks like;
01 | Predicate < Person > allowNamesWithVowels = new Predicate < Person > (){ |
02 | String[] vowels = { "a" , "e" , "i" , "o" , "u" , "y" }; |
04 | public boolean apply(Person person) { |
05 | String firstName = person.getName().toLowerCase(); |
11 | for (String vowel : vowels){ |
12 | if (firstName.startsWith(vowel)) |
Now lets build a Person set and begin working using it.
01 | Person frodo = new Person( "Frodo" , "Baggins" ); |
02 | Person sezin = new Person( "Sezin" , "Karli" ); |
03 | Person luke = new Person( "Luke" , "Skywalker" ); |
04 | Person anakin = new Person( "Anakin" , "Skywalker" ); |
05 | Person eric = new Person( "Eric" , "Draven" ); |
07 | HashSet<person> setOfPerson = |
08 | Sets.newHashSet( eric, frodo, sezin, luke, anakin ); |
09 | System.out.println( "set of person: " +setOfPerson); |
filter() will use our Predicate and create a view from elements that satisfy the
Predicate
1 | Set < Person > view = Sets.filter(setOfPerson, allowNamesWithVowels); |
2 | System.out.println( "view: " +view); |
6 | System.out.println( "new view: " +view); |
8 | System.out.println( "set of person: " +setOfPerson); |
As you can see the removal of an item from the view causes the same removal from the origin set. This removal action is bidirectional. e.g. removal from the origin set will remove the corresponding element from the view.
Lets see what happens when I add to the original list.
1 | Person bilbo = new Person( "Bilbo" , "Baggins" ); |
2 | Person alex = new Person( "Alex" , "Delarge" ); |
5 | System.out.println( "new view: " +view); |
The view is updated as well and the elements that are accepted by the predicate are added to the view. In the case above only alex is accepted. you can try add a new element to the view which does not met the predicate's requirement.
1 | Person jarjar = new Person( "Jarjar" , "Binks" ); |
The commented line above will throw java.lang.IllegalArgumentException because the view does not accept elements that does not met the predicate's requirement.
The filter() method can be called from Iterables for all Iterable objects. All live-view tricks we saw earlier are still valid. But what I'll do for working on Maps? It's possible to filter Map objects using keys or values or both (entries) as input. First, I will build a game characters to appearance map.
1 | Map < String, Integer > characterAppearancesMap = Maps.newHashMap(); |
2 | characterAppearancesMap.put( "Mario" , 15 ); |
3 | characterAppearancesMap.put( "Snake" , 8 ); |
4 | characterAppearancesMap.put( "Kratos" , 4 ); |
Now lets create the necessary Predicates. I'd like to have game characters with 'o' in their name and with lots of game appearances (more than 10 to be exact).
01 | Predicate < String > allowNamesWithO = new Predicate < String > (){ |
03 | public boolean apply(String name) { |
04 | String lowerCaseName = name.toLowerCase(); |
06 | return lowerCaseName.contains( "o" ); |
10 | Predicate < Integer > allowLotsOfAppearances = new Predicate < Integer > (){ |
12 | public boolean apply(Integer appearance) { |
13 | int numberOfAppearance = appearance.intValue(); |
15 | return (numberOfAppearance > 10 ); |
First we will filter using the names (keys) and then we will filter using the number of appearances (values).
01 | Map < String, Integer > filterKeys = |
02 | new HashMap < String, Integer > ( Maps.filterKeys(characterAppearancesMap, allowNamesWithO) ); |
04 | System.out.println( "Game Characters with 'o' in their name: " +filterKeys); |
07 | Map < String, Integer > filterValues = Maps.filterValues(filterKeys, allowLotsOfAppearances); |
09 | "Game Characters with 'o' in their name and with more than 10 appearances: " +filterValues); |
You can obtain the desired map by writing a single Predicate that will contain both predicates and use this Predicate on entries of a Map. It's not possible for Predicates.and() because we have to apply the Predicate on both keys and values but new Predicate built using and() can only be applied on keys or values. Lets create a new Predicate for our case.
01 | Predicate < Map.Entry < String, Integer > > characterPredicate = |
02 | new Predicate < Map.Entry < String, Integer > > (){ |
05 | public boolean apply(Entry<string, integer= "" > entry) { |
06 | String key = entry.getKey(); |
07 | int value = entry.getValue().intValue(); |
09 | return (key.contains( "o" ) && value > 10 ); |
14 | Map < String, Integer > filterEntries = |
15 | Maps.filterEntries(characterAppearancesMap, characterPredicate); |
16 | System.out.println( "Are the game character results same? " +filterEntries.equals(filterValues)); |
Functions class has a static method that'll convert your Predicate to a Function. The said Function will use your Predicate and return the result as a Boolean.
01 | Function < Person, Boolean > vowelVerifier = Functions.forPredicate(allowNamesWithVowels); |
02 | ArrayList < Person > people = Lists.newArrayList(anakin, bilbo, alex); |
03 | List< boolean > transform2 = Lists.transform(people, vowelVerifier); |
06 | System.out.println( "Result: " +transform2); |
CharMatcher is basically a Predicate who accepts Character (instead of objects). Due to this fact you can easily convert your Predicate to a CharMatcher. Lets assume that we have a Predicate
that allows vowels only.
01 | Predicate < Character > allowVowel = new Predicate < Character > (){ |
02 | char [] vowels = { 'a' , 'e' , 'i' , 'o' , 'u' , 'y' }; |
04 | public boolean apply(Character character) { |
05 | char lowerCase = Character.toLowerCase(character.charValue()); |
07 | for ( char chr : vowels){ |
With forPredicate() we can build a CharMatcher from a Predicate. Lets use our new CharMatcher for counting vowels in a String.
1 | CharMatcher vowelMatcher = CharMatcher.forPredicate(allowVowel); |
2 | int vowelCount = vowelMatcher.countIn( "starkiller" ); |
3 | System.out.println( "Vowel Count in 'starkiller': " +vowelCount); |
Predicates class contains several useful methods for creating Predicates. If you plan using Predicates you should take a look. Lets see its methods.
Assume that we don't want people with long last names. Our character limit can be 8 for instance. Lets write a Predicate for the purpose.
01 | Predicate < Person > allowShortLastNames = |
02 | new Predicate < Person > (){ |
04 | public boolean apply(Person person) { |
05 | String lastName = person.getLastName(); |
07 | return (lastName.length() < 8 ); |
11 | System.out.println(setOfPerson); |
14 | Set<person> onlyShortLastNames = |
15 | Sets.filter(setOfPerson, allowShortLastNames); |
17 | "People with short last names: " +onlyShortLastNames); |
Because Skywalker is more than 7 characters it's filtered out. All other last names are in line with the predicate but what if I want a more complicated Predicate? For instance one which will accept people with first name that begins with a vowel and with short last name. I can combine 2 Predicates in this case.
1 | Predicate < Person > combinedPredicates = |
2 | Predicates.and(allowShortLastNames, allowNamesWithVowels ); |
3 | Set < Person > filter = Sets.filter(setOfPerson, combinedPredicates); |
4 | System.out.println( "Combined Predicates: " +filter); |
We know that longer last names will return false and we will be left with shorter last names. e.g. [Bilbo Baggins, Alex Delarge, Sezin Karli, Frodo Baggins].Then we will apply the vowel Predicate which will give [Alex Delarge] because Alex is the only first name that begins with a vowel. All Predicates that are combined will be applied for each element in the Iterable (Set, here). If one of the Predicates are false then the overall result is false else it will return true.
There are three and() methods. We show the one that'll work with 2 Predicates. There's a version that'll work with more than 2 Predicates (var-arg as argument). The last one takes Predicates from an Iterable that contains them (Iterable ). We previously used the boolean and() operator. It's also possible to combine Predicates with or() operator. or() will return true even one of the results is true if none is true then it'll return false. Assume that I want either short first names or short last names. I have a Predicate for last names. Lets add a new one for first names.
01 | Predicate < Person > allowShortFirstNames = new Predicate < Person > (){ |
03 | public boolean apply(Person person) { |
04 | String firstName = person.getName(); |
06 | return (firstName.length() < 8 ); |
10 | Predicate < Person > predicatesCombinedWithOr = |
11 | Predicates.or(allowShortLastNames, allowShortFirstNames); |
12 | filter = Sets.filter(setOfPerson, predicatesCombinedWithOr); |
13 | System.out.println(setOfPerson); |
14 | System.out.println( "Combined with or:" +filter); |
22 | setOfPerson.add( new Person( "Abdullah" , "Jabbarian" )); |
23 | filter = Sets.filter(setOfPerson, predicatesCombinedWithOr); |
24 | System.out.println( "Combined with or 2: " +filter); |
It's possible to negate the Predicate in hand using not() method. Lets negate the combined predicate we created using and(). Normally we are accepting people with first name that begins with a vowel and with short last name. If we negate this the Predicate will accept people with first name that begin with a consonant and with a long last name.
01 | Predicate<person> negatedPredicates = |
02 | Predicates.not(combinedPredicates); |
09 | filter = Sets.filter(setOfPerson, negatedPredicates); |
10 | System.out.println( "Negated and combined Predicates: " +filter); |
Anakin is the only one with a vowel in the first name but as his last name is long he's allowed by the Predicate.
You can create two interesting Predicates using isNull() and notNull(). isNull() (notNull()) returns a Predicate that returns true if the object in hand is null (not null).
02 | System.out.println(setOfPerson); |
06 | Predicate < Person > notNullPredicate = Predicates.notNull(); |
09 | Set < Person > filter2 = Sets.filter(setOfPerson, notNullPredicate); |
10 | System.out.println(filter2); |
As you can see the null value is not present in the new view Set.
It's possible to use Predicates' contains() or containsPattern() methods for building a Predicate that'll accept only CharSequences that contain the given pattern. contains() takes a Pattern object while containsPattern() takes a regular expression as a String.
02 | Predicate < CharSequence > containsPattern = |
03 | Predicates.containsPattern( "[0-9]" ); |
04 | Set<string> sw = Sets.newHashSet( "r2-d2" , "luke" , "c3po" , "darth" ); |
05 | Set<string> filter3 = Sets.filter(sw, containsPattern); |
06 | System.out.println( "New set of sw characters " +filter3); |
09 | Predicate < CharSequence > containsPattern2 = |
10 | Predicates.contains( Pattern.compile( "[0-9]" ) ); |
11 | Set < String > filter4 = Sets.filter(sw, containsPattern2); |
12 | System.out.println( "Do they have the same result? " +filter4.equals(filter3)); |
Another useful method of Predicates is in(). It allows elements that are inside a given Collection and rejects all others.
01 | ArrayList < String > fruitList = |
02 | Lists.newArrayList( "apple" , "melon" , "strawberry" ); |
03 | Predicate < String > fruitPredicate = Predicates.in(fruitList); |
04 | List < String > fruitBasket = |
05 | Lists.newArrayList( "apple" , "mango" , "melon" , "banana" , "apple" , "melon" , "strawberry" , "melon" , "strawberry" ); |
06 | Iterable < String > filter5 = Iterables.filter(fruitBasket, fruitPredicate); |
09 | System.out.println( "Filtered Fruit Basket: " +filter5); |
16 | fruitBasket.retainAll(fruitList); |
17 | System.out.println( "Filtered Fruit Basket: " +fruitBasket); |
Predicates' instanceOf() checks if the object in hand is an instance of the given Class.
01 | Predicate < Object > listPredicate = Predicates.instanceOf(List. class ); |
02 | List < Collection < collectionsList = |
03 | new ArrayList < Collection > (); |
04 | collectionsList.add( new HashSet()); |
05 | collectionsList.add( new ArrayList()); |
06 | collectionsList.add( new LinkedList()); |
07 | collectionsList.add( new TreeSet()); |
08 | collectionsList.add( new LinkedHashSet()); |
10 | Collection < Collection > filter6 = |
11 | Collections2.filter(collectionsList, listPredicate); |
12 | System.out.println( "Are only Lists filtered? " |
13 | + (filter6.size() == 2 ) ); |
Only instances of List are allowed so the result contains only the ArrayList and LinkedList.
Another interesting method of Predicates is compose(). compose() method allows you to process all your elements using the given Function and apply a Predicate on the result. You can do that in 2 steps but when you can do that in a single step do you need it? I have a set of objects of type Circle. I'd like to filter Circles with their area less than a value. It's possible do the calculation of the area in the Predicate or we can put the method in a helper class (e.g. Circles.getArea()). Another possibility is to write a Function for this operation.
01 | Function < Circle, Double > calculateArea = |
02 | new Function < Circle, Double > () { |
05 | public Double apply(Circle circle) { |
06 | double radius = circle.getRadius(); |
07 | double area = Math.PI * radius * radius; |
13 | Predicate < Double > allowCirclesWithLargeArea = |
14 | new Predicate < Double > () { |
17 | public boolean apply(Double area) { |
22 | Predicate < Circle > allowCircles = |
23 | Predicates.compose(allowCirclesWithLargeArea, calculateArea); |
25 | HashSet < Circle > circleSet = |
26 | Sets.newHashSet( new Circle( 3.5 , 4.5 , 0.3 ), |
27 | new Circle( 2.5 , 1.5 , 0.03 ), new Circle( 3.4 , 4.6 , 4.3 ), |
28 | new Circle( 3.5 , 6.5 , 5.3 ), new Circle( 0.5 , 4.5 , 12.3 )); |
30 | Set < Circle > filter7 = |
31 | Sets.filter(circleSet, allowCircles); |
33 | "Allow circle with area less than 10: " +filter7); |
Each circle is first processed by calculateArea Function and their areas are obtained. Then allowCirclesWithLargeArea Predicate can be used for filtering the areas in hand.
Iterables class contains several methods for working using Predicates. all() and any() are quite straightforward as methods. all (any) returns true if all (any) elements in the given Iterable satisfy the given Predicate. I'm going to create Band class for storing two attributes of a Band (its name and country of origin). Then create a country Predicate so we can see what all() and any() do.
03 | public Band(String name, String country) { |
05 | this .country = country; |
07 | public String getName() { |
11 | public void setName(String name) { |
15 | public String getCountry() { |
19 | public void setCountry(String country) { |
20 | this .country = country; |
24 | public String toString() { |
25 | return "{name= " +name+ ", country= " +country+ "}" ; |
Now lets create the bands.
01 | Band stratovarius = new Band( "Stratovarius" , "Finland" ); |
02 | Band amorphis = new Band( "Amorphis" , "Finland" ); |
04 | Set < Band > metalBands = |
05 | Sets.newHashSet(stratovarius, amorphis); |
07 | Predicate < Band > finlandPredicate = |
08 | new Predicate < Band > () { |
10 | public boolean apply(Band band) { |
12 | band.getCountry().equals( "Finland" ); |
27 | Iterables.all(metalBands, finlandPredicate); |
29 | "every band is from Finland? " +all); |
35 | Band mezarkabul = new Band( "Mezarkabul" , "Turkey" ); |
36 | metalBands.add(mezarkabul) ; |
38 | all = Iterables.all(metalBands, finlandPredicate); |
40 | "every band is from Finland? " +all); |
48 | boolean any = Iterables.any(metalBands, finlandPredicate); |
50 | "at least one band is from Finland? " +any); |
Lets remove all bands form Finland and see any()s behavior. There are more than one way to do that. The most straightforward way is to use removeIf() method of Iterables class which removes elements that satisfy the given Predicate from the given Iterable.
1 | Iterables.removeIf(metalBands, finlandPredicate); |
3 | System.out.println(metalBands); |
8 | any = Iterables.any(metalBands, finlandPredicate); |
9 | System.out.println( "at least one band is from Finland? " +any); |
Iterables' filter() method can be used for filtering any Iterable using Predicates. It's similar to Collections2's filter method but usable for a larger number of Classes.
Iterables class has two more interesting methods. indexOf() which returns the index of the first element that satisfies the given Predicate. If it cant find a matching element it returns -1. The other method is find() which returns the first element which satisfies the given Predicate. If it cant find a matching element it throws NoSuchElementException. To sum up find() returns the object, indexOf() returns its index in the Iterable. As find() throws an exception in the case it couldnt find a matching element it would be wise to check first with indexOf() and then either use the index to obtain the object or to use find().
01 | metalBands.add( new Band( "Lordi" , "Finland" )); |
08 | if (Iterables.indexOf(metalBands, finlandPredicate) != - 1 ){ |
11 | Band bandFromFinland = |
12 | Iterables.find(metalBands, finlandPredicate); |
13 | System.out.println( "The first band from Finland: " |
This is all for the Predicates. I hope I was able to give you a taste of functional programming in Java using Guava.