Thursday, June 8, 2017

Use Precise Java Method Parameters

Learn how to pick the right method parameter types and get more robust and shorter code in your Java applications.

We Java developers generally have a bad habit of using method parameters without thinking of what is actually needed and just picking whatever we are used to, what we have available or whatever first comes into mind. Consider the following representative example:
 
    private static String poem(Map<Integer, String> numberToWord) {
        return new StringBuilder()
            .append("There can be only ")
            .append(numberToWord.get(1))
            .append(" of you.\n")
            .append("Harts are better off when there are ")
            .append(numberToWord.get(2))
            .append(" of them together.\n")
            .append("These ")
            .append(numberToWord.get(3))
            .append(" red roses are a symbol of my love to you.\n")
            .toString();
    }

When we use the method above, we provide a Map that translates from a number to a String. We might, for example, provide the following map:

    Map<Integer, String> englishMap = new HashMap<>();
        englishMap.put(1, "one");
        englishMap.put(2, "two");
        englishMap.put(3, "three");

When we call our poem method with the englishMap then the method will produce the following output:
 
There can be only one of you.
Harts are better off when there are two of them together.
These three red roses are a symbol of my love to you.

That sounds good. Now suppose that your significant other is a computer nerd and you want to spice up your poem and make an impression, then this is the way to go:

    Map<Integer, String> nerdMap = new HashMap<>();
        nerdMap.put(1, "1");
        nerdMap.put(2, "10");
        nerdMap.put(3, "11");

If we now submit our nerdMap to the poem method, it will produce the following poem:
 
There can be only 1 of you.
Harts are better off when there are 10 of them together.
These 11 red roses are a symbol of my love to you.

As with all poems, it is difficult to judge which poem is more romantic that the other but I certainly have my own personal view.
 


The Problems

There are several problems with the solution above:

First of all, as an outside caller, we cannot be sure that the poem method does not change the Map we provide. After all, we provide a Map and there is nothing preventing a receiver to do whatever possible with the map, even clearing the entire map altogether. This can of course be avoided by wrapping the Map using the Collections.unmodifiableMap() method or provide a copy of an existing map whereby the copy is later discarded.

Secondly, we are tied to use a Map when we only need something that translates from an integer to String. This might create unnecessary code in some cases. Think back of our nerdMap, where the values in the map could easily be computed using the Integer::toBinaryString instead of mapping them manually.

The Solution

We should strive to provide precisely what is needed in any given situation and not more. In our example we should modify the poem method to take a function that goes from an integer to a String. How this function is implemented on the caller side is of less importance, it can be a map or a function, or code or something else. Here is how it should be done in the first place:
 
    private static String poem(IntFunction<String> numberToWord) {
        return new StringBuilder()
            .append("There can be only ")
            .append(numberToWord.apply(1))
            .append(" of you.\n")
            .append("Harts are better off when there are ")
            .append(numberToWord.apply(2))
            .append(" of them together.\n")
            .append("These ")
            .append(numberToWord.apply(3))
            .append(" red roses are a symbol of my love to you.\n")
            .toString();
    }
 
If we want to use the poem method with a Map, we simply call it like this:
 
    // Expose only the Map::get method
    System.out.println(poem(englishMap::get));

If we want to compute the values like we did for the the nerd poem then we can do it even simpler:


    System.out.println(poem(Integer::toBinaryString));

Heck, we can even produce a poem to a significant other suffering from a dual personality disorder like this:
 
    System.out.println(
        poem(
            no -> englishMap.getOrDefault(no + 1, Integer.toString(no + 1))
        )
    );

This will produce the following poem:

  
There can be only two of you.
Harts are better off when there are three of them together.
These 4 red roses are a symbol of my love to you.

Be careful with your method parameters!