17 April 2010

Creating a list with the results of calling a method on each element of another list

Valid since: op4j 1.0

Description
Extract one attribute from each of the objects in a list and create another list with the results.

Scenario
Our User class is a value object class like:
public class User {
    private String name;
    ...
    public String getName() {
        return this.name;
    }
}
Given the List<User> users variable, containing:
// users == LIST [ 
//                 USER [ ...; name="James Cheddar"; ...]
//                 USER [ ...; name="Richard Stilton"; ...]
//                 USER [ ...; name="Bernard Brie"; ...]
//                 USER [ ...; name="Antonio Cabrales"; ...]
//               ]
...we want to obtain a List<String> with all the names of the users, like:
// users == LIST [ "James Cheddar", "Richard Stilton", 
//                 "Bernard Brie", "Antonio Cabrales" ]

Recipe
Map (iterate + execute) a Get function, which will call the getName() method:

List<String> names =
    Op.on(users).map(Get.attrOfString("name")).get();

Get.attrOfString(...) allows you to call the getter method for the name attribute (which is a String), and is equivalent in fact to calling the getName() method using a Call function:

List<String> names =
    Op.on(users).map(Call.methodForString("getName")).get();

Comments
Of course, mapping is equivalent to iterating and executing, so this will be equivalent to:
List<String> names =
    Op.on(users).forEach().exec(Get.attrOfString("name")).get();
Now, let's see the equivalent non-op4j Java code:
List<String> names = new ArrayList<String>();
for (User user : users) {
    names.add(user.getName());
}

But... what if users had been an array instead of a list? Well, because arrays need to be instantiated using their specific class in Java (and not a generic one like java.util.List<?>), our map action will need to specify the type of the result coming from the Get function, like:
...map(Types.STRING, Get.attrOfString("name"))...
}
And also, because of User not being a standard basic Java class like String or Integer, but instead a user-defined one, if we create our operation expression with:
Op.on(userArray)...
// User is not a basic Java class, so our operator will be limited!
...we will get a limited operator, which will let us do some things (like converting to a list or set, for instance), but not iterating or executing functions on its elements. For that, instead, we will need to specify the Type of the array's elements as a javaRuntype type (similar to that Types.STRING, but for our own User class):
Type<User> userType = Types.forClass(User.class);
Op.onArrayOf(userType, usersArray)...
Finally, putting it all together:
Type<User> userType = Types.forClass(User.class);
String[] names =
    Op.onArrayOf(userType, usersArray).
        map(Types.STRING, Get.attrOfString("name")).get();
Which compares to the equivalent non-op4j Java code:
List<String> namesList = new ArrayList<String>();
for (User user : usersArray) {
    namesList.add(user.getName());
}
String[] names = namesList.toArray(new String[namesList.size()]);

No comments:

Post a Comment