Home > Enterprise >  Functions instead of static utility methods
Functions instead of static utility methods

Time:11-10

Despite Functions being around in Java since Java 8, I started playing with them only recently. Hence this question may sound a little archaic, kindly excuse.

At the outset, I am talking of a pure function written completely in conformance of Functional Programming definition: deterministic and immutable.

Say, I have a frequent necessity to prepend a string with another static value. Like the following for example:

    private static Function<String, String> fnPrependString = (s) -> {
        return "prefix_"   s;
    };

In the good old approach, the Helper class and its static methods would have been doing this job for me.

The question now is, whether I can create these functions once and reuse them just like helper methods.

One threat is that of thread-safety. And I used a simple test to check this with this JUnit test:

package com.me.expt.lt.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.function.Function;

import org.junit.jupiter.api.Test;

import com.vmlens.api.AllInterleavings;

public class TestFunctionThreadSafety {
    private static Function<String, String> fnPrepend = (s) -> {
        System.out.println(s);
        return new StringBuffer("prefix_").append(s).toString();
    };

    @Test
    public void testThreadSafety() throws InterruptedException {
        try (AllInterleavings allInterleavings = new AllInterleavings(
                TestFunctionThreadSafety.class.getCanonicalName());) {
            ConcurrentMap<String, Integer> resultMap = new ConcurrentHashMap<String, Integer>();
            while (allInterleavings.hasNext()) {
                int runSize = 5;
                Thread[] threads = new Thread[runSize];
                ThreadToRun[] ttrArray = new ThreadToRun[runSize];
                StringBuffer sb = new StringBuffer("0");
                for (int i = 0; i < runSize; i  ) {
                    if (i > 0)
                        sb.append(i);
                    ttrArray[i] = new ThreadToRun();
                    ttrArray[i].setS(sb.toString());
                    threads[i] = new Thread(ttrArray[i]);
                }
                for (int j = 0; j < runSize; j  ) {
                    threads[j].start();
                }
                for (int j = 0; j < runSize; j  ) {
                    threads[j].join();
                }
                System.out.println(resultMap);
                StringBuffer newBuffer = new StringBuffer("0");
                for (int j = 0; j < runSize; j  ) {
                    if(j>0)
                        newBuffer.append(j);
                    assertEquals("prefix_"   newBuffer, ttrArray[j].getResult(), j   " fails");
                }
            }
        }
    }

    private static class ThreadToRun implements Runnable {
        private String s;
        private String result;

        public String getS() {
            return s;
        }

        public void setS(String s) {
            this.s = s;
        }

        public String getResult() {
            return result;
        }

        @Override
        public void run() {
            this.result = fnPrepend.apply(s);
        }

    }

}

I am using vmlens. I can tune my test by changing the runSize variable by as good a number as I choose so that the randomness can be checked. The objective is to see if these multiple threads using the same function mix up their inputs because of concurrent access. The test did not return any negative results. Please also do comment on whether the test meets the goals.

I also tried to understand the internal VM end of how lambdas are executed from here. Even as I look for somewhat simpler articles that I can understand these details faster, I did not find anything that says "Lambdas will have thread safety issues".

Assuming the test case meets my goal, the consequential questions are:

  1. Can we replace the static helper classes with function variables immutable and deterministic functions like fnPrepend? The objective is to simply provide more readable code and also of course to move away from the "not so Object oriented" criticism about static methods.

  2. Is there is a source of simpler explanation to how Lambdas work inside the vm?

  3. Can the results above with a Function<InputType, ResultType> be applied to a Supplier<SuppliedType> and a Consumer<ConsumedType> also?

Some more familiarity with functions and the bytecode will possibly help me answer these questions. But a knowledge exchange forum like this may get me an answer faster and the questions may trigger more ideas for the readers.

Thanks in advance. Rahul

CodePudding user response:

I really don't think you, as a user, need to go to such lengths to prove the JVM's guarantees about lambdas. Basically, they are just like any other method to the JVM with no special memory or visibility effects :)

Here's a shorter function definition:

private static Function<String, String> fnPrepend = s -> "prefix_"   s;

this.result = fnPrepend.apply(s);

... but don't use a lambda just for the sake of it like this - it's just extra overhead for the same behaviour. Assuming the real usecase has a requirement for a Function, we can use Method References to call the static method. This gets you the best of both worlds:

  // Available as normal static method
  public static String fnPrepend(String s) {
    return "prefix_"   s;
  }
  
  // Takes a generic Function
  public static void someMethod(UnaryOperator<String> prefixer) {
    ...
  }

  // Coerce the static method to a function
  someMethod(Util::fnPrepend);


  • Related