Benefit of using Java Function rather than normal method?

Viewed 3436

The Function Interface is introduced in Java 8, to implement functional programming in Java. It represents a function that takes in one argument and produces a result. It's easy to practise and read, but I am still trying to understand the benefit of it other than just making it look cool. For example,

Function<Integer, Double> half = a -> a / 2.0;
Function<Double, Double> triple = b -> b * 3;
double result = half.andThen(triple).apply(8);

can just be converted as a standard method like

private Double half(int a) {
    return a / 2.0;
}
private Double triple (int b) {
    return b * 3;
}
double result = triple(half(8));

So what's the benefit of using Function? As it refers to functional programming, what exactly is functional programming in Java and benefit it could bring? Would it benefit the way like:

  1. execution of chaining functions together (e.g andThen & compose)
  2. usage inside Java Stream?
  3. the access modifier as function tends to define with private not public, while method can be either?

Basically, I'm curious to know, in what circumstances would we prefer using function rather than normal method? Is there any use case that's unable or difficult to use, or converted with a normal method?

8 Answers

One usage of Function is in Streams. Everyone uses map method these days, I believe:

This map method accepts the Function as a parameter. This allows writing a pretty elegant code - something that could not be achieved before Java 8:

Stream.of("a", "b", "c")
   .map(s -> s.toUpperCase())
   .collect(Collectors.toList());
// List of A, B, C

Now its true that there are method references and functional interfaces (one of which is Function of course), this lets you using method reference to rewrite the above example as:

Stream.of("a", "b", "c")
    .map(String::toUpperCase)
    .collect(Collectors.toList())

... but that's only a syntactic sugar - map still accepts the Function as a parameter of course.

Another example that uses Function from Java itself is StackWalker: Here is an example:

List<StackFrame> frames = StackWalker.getInstance().walk(s ->
    s.dropWhile(f -> f.getClassName().startsWith("com.foo."))
     .limit(10)
     .collect(Collectors.toList()));
}

Note the call to walk method - it accepts a function as a parameter.

So bottom line, it's just yet another tool that can help the programmer to express his/her intentions. Use it wisely wherever appropriate.

Suppose I want to write an applyTwice function:

double applyTwice(double x, Function<Double, Double> f) {
  return f.apply(f.apply(x));
}

This needs the function be represented as an object.

Functions are useful when you want to put some structure around arbitrary code supplied by the caller.

One example I had to use just a few days ago at my workplace is when I wanted to lazily compute a message, depending on a condition. For example imagine a logger usage like this:

  logger.debug("my-heavy-computed-message-here");

Now imagine that the computation of "my-heavy-computed-message-here" is really just that - it is heavy to compute; but you only want to present it if the DEBUG logger is enabled. What people usually do is:

if(logger.isDebugEnabled()) {
    logger.debug("my-heavy-computed-message-here");
}

This is ugly. Instead, we have some code in place that takes a Function (or Supplier) as input:

 logger.debug(Function<SomeObject, String> function)

Internally in our logger implementation we call function::apply (thus computing that expensive String) only as needed (or in a 'lazy' fashion).

In Java it's usually called "pure functions", which are defined alike:

  • The execution of the function has no side effects.

  • The return value of the function depends only on the input parameters passed to the function.

Anything else should be an object's method.

Instead of inheritance with overrides, i.e. anonymous instances, pass function-ality.

Say you create a class, but one bit of calculation must be provided.

class C {
    protected abstract int f(int x);
}

class Child1of99 extends C {
    @Override
    protected int f(int x) { return x / 42; }
}

or

new C() {
    @Override
    protected int f(int x) { return x / 42; }
}

Alternatively you can do:

class C {
    private final IntOperation f;

    C(IntOperation f) {
        this.f = f;
    }
}

 new C(x -> x / 42);

As far as functional interfaces are concerned, I believe it makes "behaviours" more agile, what I mean by that is with the help of functional interface you can easily and quickly provide behaviour to other members and that's not the case with conventional methods. So, basically with instance methods your behaviour stick to the objects, alternatively static methods can provide a better scope and may be accessible across all classes but again it's not dynamic at all.

Consider following example,

class Math {

    int sum(int a, int b) {
        return a + b;
    }
}

Now my sum method is fix and can not be changed, now consider the following example with functional interfaces,

interface Sum {
    int sum(int a, int b);
}

Now I can have different behaviours,

Sum nocheckSum = (a, b) -> a + b;
Sum positiveNumSum = (a, b) -> {
                                    if(a < 0 || b < 0) throw new IllegalArgumentException("Only positive numbers are allowed!");
                                    return a + b;
                               };

May be this is not the best example but I guess you got my point.

Now the benefit of this mechanism is that you don't need to declare and manage methods for different behaviours, you can dynamically create one and use it for specific purposes. At the same it's also important to mention that if you are sure that the behaviour has to be common for all who consume it, then I would not recommend to enforce functional interfaces but if it can and may change for not all but specific group of method consumers then definitely functional interfaces can be helpful.

Bottom line is, both either method or functional interfaces have their own significance in the language, even if we can use them interchangeably more better option would be to consciously choose one of them based on your business requirements.

In general, functional programming (lambdas, functional interfaces) serves the best operations like transformations and processing. In contrast, OOP programming (using methods) works the best when you have to store data (f.ex in memory), mutate it from time to time, or send a message between components. As usual, it depends. To all generalizations, you have exceptions.

Basically, Java function are built in, error free, optimized, powerful functions that fit your requirements. Developers used best algorithms in Java functions to reduce the time and space complexity.

Related