Tuesday, July 23, 2013

Software Engineering: Java Tuples

This post is related to Java Tuples. Every time I'm switching from Scala or Python back to Java what I'm missing are tuples. Simple but effective idea of tuples minimizes the amount of code needed to express my ideas in code.

Sure thing you don't want to publish an API with Tuples as a return type of your methods. But if the tuples are used between your methods - they can significantly reduce the amount of work and a number of classes needed. So I've implemented my own library to add Tuples to Java. Feel free to try it. Sources on GitHub

<dependency>
    <groupId>com.othelle.jtuples</groupId>
    <artifactId>jtuples</artifactId>
    <version>{always.try.to.pick.the.latest.version}</version>
</dependency>


I would be more than happy to help you if you get stuck somewhere.

In the rest part of the post I'm going to show you why it might be useful to take a look at this library.

Tuple as a key in Java Map. 

What I really like to see in Tuples library is an ability to use them as keys in a map. So if you need to use a composite key (say firstName, lastName) - it should be possible to write something like this. Without worrying about hashCode() and equals() to be implemented for a wrapper class. Just use tuples out of the box.
    HashMap<Tuple2, String> title = new HashMap<Tuple2, String>();

    title.put(tuple("John", "Snow"), "Bastard1");
    title.put(tuple("John", "Snow"), "Bastard2");
    assertThat(title.get(tuple("John", "Snow")), 
        Matchers.equalTo("Bastard2"));

Sometimes I'm using a short list of objects as a key in map. Using the lists as a key is not a good idea (because of standard hashCode and equals methods), so you can end-up using something like this (make sure your lists aren't longer then 16 elements)

    Tuple tuple1 = Tuples.convert(Arrays.asList(1, "2", "3", 1, "5"));
    Tuple tuple2 = Tuples.convert(Arrays.asList(1, "2", "3", "1", 5));
    Tuple tuple3 = Tuples.convert(Arrays.asList(1, "2", "3", 1, "5"));

    map.put(tuple1, "1");
    map.put(tuple2, "2");
    map.get(tuple3); // returs "1"

Notice, that behavior is possible since tuples are immutable objects and they have overridden hashCode() and equals() methods. If you want to create your own implementation of Tuples it worth looking at Product class and probably derive from it.

Tuple as a result of merging from several sources. 

From time to time it's required to merge a list of results from different sources. Imagine we have lists of firstNames, lastNames and salaries and we need to have them merged together.

Instead of writting something like this:
 public List<SimpleContact> mergeFirstLastNameAndSalary(List<String> firstNames, 
        List<String> lastNames, List<Integer> salaries) {
        //assume all the lists are the same size
        ArrayList<SimpleContact> result = new ArrayList<SimpleContact>();
        Iterator<String> lnameIterator = lastNames.iterator();
        Iterator<Integer> salIterator = salaries.iterator();
        for (String firstName : firstNames)
            result.add(new SimpleContact(firstName, lnameIterator.next(), salIterator.next()));
        return result;
    }


    public static class SimpleContact {
        private String firstName;
        private String lastName;
        private Integer salary;

        public SimpleContact(String firstName, String lastName, 
              Integer salary) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.salary = salary;
        }

        public String getFirstName() {
            return firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public Integer getSalary() {
            return salary;
        }
    }

We simply use

 public List<Tuple3<String, String, Integer>> mergeFirstLastNameAndSalaryAsTuple(List<String> firstNames, List<String> lastNames, List<Integer> salaries) {
        return ZipUtils.zip(firstNames, lastNames, salaries);
    }
short enough isn't it? Once again, I understand the you don't want to give someone an API with Tuple as a result. So the main idea is to use Tuples between your private or protected methods or within the bodies of your methods.

 Let's consider another example. We have a method that queries database for all the possible mappings for a given list of tags (tags are expressed as a list of strings).

 private List<Tuple4<String, Integer, Integer, Integer>> mapTagsToEntities(List<String> tags) {
        return ZipUtils.zip(tags, mapTagsToGenes(tags), mapTagsToNWObjects(tags), mapTagsToProcesses(tags));
    }

        List<Integer> mapTagsToGenes(tags); 
 List<Integer> mapTagsToNWObjects(tags); 
 List<Integer> mapTagsToProcesses(tags);

 
Each of these three maxXXX methods can be implemented by querying one or two tables in a pretty effective manner. We only focusing on implementing our mapXXX methods not on merging the results together.
If it's needed to transform this Tuple4 to any wrapping class - Lists.transform(...) from com.google.collections can be used. 

Imagine after getting this list of Tuples we want to build a HashMap to query for mapping by tag. This can be achieved either by iterating through the List and putting values one by one or by using com.othelle.jtuples.MapUtils:

        List<Tuple4<String, Integer, Integer, Integer>> tagToEntiries = mapTagsToEntities(Arrays.asList("tag1", "tag2", "teg3"));
        Map<String, Tuple3<Integer, Integer, Integer>> lookupByTagMap = MapUtils.map4(tagToEntiries);
        lookupByTagMap.get("tag2"); // lookup by tag. 

Sometimes we need to pass a map as a parameter to a function.
    Map params = new HashMap();
    params.put("key1", "value1");
    params.put("key2", "value2");
    params.put("key3", "value3");
        
    callMethodWithMapAsParam(params); //call the actual method

with JTuples it can be done in such manner:
import static com.othelle.jtuples.Tuples.tuple;
...
callMethodWithMapAsParam(MapUtils.map(tuple("key1", "value1"), 
          tuple("key2", "value2"), tuple("key3", "value3"))); 


It would take a lot of time and space to play with all possible situations where using of Tuples might be beneficial. So, I strongly encourage you to take a look at this library and play with it by yourself. 

No comments:

Post a Comment