Functional Programming in Java. Lambda expressions


Topics interesting only for Java programmers

Link to this posting

Postby Ursego » 05 Mar 2019, 07:53

Copy the following fragment to a text file with .java extension.

To enjoy keywords coloring, open this file in Eclipse. After that, enable word-wrap by pressing Alt + Shift + Y (otherwise you will need to scroll right to read long comments). Set the code editor width so the very first line of the file (with asterisks) fits one line (i.e. is not broken into 2 lines).

You can also open this file in Notepad++. It will understand by the file extension (.java), that it's Java code, and paint keywords. After that, enable word-wrap by marking the menu option View > Word wrap.

In any editor, use the Fixedsys font (as I did) to see straight vertical alignment.

Code: Select all
// A functional interface as an interface that contains a single abstract method. There can be additional static method and default methods, but the abstract method must be exactly one. If interface B extents a functional interface A and adds another abstract method, then interface B is not functional (since it has two abstract method altogether).

// While it is a good practice to mark a functional interface with the @FunctionalInterface annotation for clarity, it is not required with functional programming. The Java compiler implicitly assumes that any interface that contains exactly one abstract method is a functional interface. Conversely, if a class marked with the @FunctionalInterface annotation contains more than one abstract method, or no abstract methods at all, then the compiler will detect this error and not compile.

// Functional interfaces are used as the basis for lambda expressions in functional programming. A lambda expression is a block of code that gets passed around, like an anonymous method. Lambda expressions rely on the notion of deferred execution. Deferred execution means that code is specified now but runs later. Even though the execution is deferred, the compiler will still validate that the code syntax is properly formed.

// @@@ Lambda Syntax:

// Lambda's pattern:
<arguments> -> <body, i.e. the code to execute>

// IMPORTANT RULES:
// 1. The arguments (their number, data types and order) must be same as the arguments of the corresponding functional interface's abstract method.
// 2. The body must return the same data type (or void) as the corresponding functional interface's abstract method.

// Example:
(Animal a) -> { return a.canHop(); }

// Parentheses () for the lambda's arguments:
// The parentheses () can be omitted if there is exactly one input parameter and the type is not explicitly stated in the expression. This means that expressions that have zero or more than one input parameter (or one parameter with data type mentioned) will still require parentheses. Examples of valid arguments to a lambda:
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Lambda                  Comment                              Can be used by a functional interface that takes...      And returns...
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------
() -> new Duck()            // zero arguments                      no arguments                                 Duck object
d -> {return d.quack();}      // one argument without data type mentioned   Duck as input                                 whatever the return type of quack() is
(Duck d) -> d.quack()         // one argument with data type mentioned   Duck as input                                 whatever the return type of quack() is
(Animal a, Duck d) -> d.quack()   // more than one argument               Animal and Duck objects as input                  whatever the return type of quack() is
(a, d) -> d.quack()            // more than one argument               Animal and Duck objects as input                  whatever the return type of quack() is
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------

// Braces {} for the lambda's body:
// A pair of statement braces {} around the body of the lambda expression allows you to write multiple lines of code in the body of the lambda expression, as you might do when working with an if statement or while loop. When you add braces {}, you must explicitly terminate each statement in the body with a semicolon;. Instead of "{return d.quack();}", the body can be "d.quack()", i.e. we can omit the braces {}, semi-colon;, and return statement because this is a special shortcut that Java allows for single-line lambda bodies. This special shortcut doesn't work when you have two or more statements. This is consistent with using {} to create blocks of code elsewhere in Java. When using {} in the body of the lambda expression, you must use the return statement if the functional interface method that lambda implements returns a value. A return statement is optional when the return type of the method is void.
(String a) -> a.startsWith("test")   /* is same as */ (String a) -> {return a.startsWith("test");}
(int x) -> {}                  /* is same as */ (int x) -> {return;}

// Some examples which don't compile:
a, b -> a.startsWith("test") // needs parentheses () around the parameter list. Remember that the parentheses are optional only when there is one parameter and it doesn't have a type declared
c -> return 10; // uses the return keyword without using braces {}
a -> { return a.startsWith("test") } // is missing the semicolon after the return statement
// The following rewritten lambda expressions are each valid:
(a, b) -> a.startsWith("test")
c -> { return 10; }
a -> { return a.startsWith("test"); }

// When at least one parameter has a data type listed, ALL parameters must provide a data type. The following lambda expressions are each invalid for this reason:
(int y, z) -> {int x = 1; return y + 10; } // DOES NOT COMPILE
(String s, z) -> { return s.length() + z; } // DOES NOT COMPILE
(a, Animal b, c) -> a.getName() // DOES NOT COMPILE
// If we add or remove all of the data types, then these lambda expressions do compile.

// Since Java doesn't allow us to re-declare a local variable, the following doesn't compile:
(a, b) -> { int a = 0; return 5;} // DOES NOT COMPILE
// We tried to re-declare a, which is not allowed. By contrast, the following line is permitted because it uses a different variable name:
(a, b) -> { int c = 0; return 5;}

// @@@ Passing a lambda to a method:
// A lambda expression can be passed to a method as an argument if the data type of that argument is the corresponding functional interface:
public class Animal {
   private String species;
   private boolean canHop;
   private boolean canSwim;
   public Animal(String speciesName, boolean hopper, boolean swimmer) { species = speciesName; canHop = hopper; canSwim = swimmer; }
   public boolean canHop() { return canHop; }
   public boolean canSwim() { return canSwim; }
   public String toString() { return species; }
}

@FunctionalInterface
public interface CheckTrait {
   public boolean test(Animal a);
}
// The following simple program uses a lambda expression to determine if some sample animals match the specified criteria:
public class FindMatchingAnimals {
   private static void print(Animal animal, CheckTrait trait /* THE TYPE OF "trait" ARG IS A FUNCTIONAL INTERFACE, SO WE WILL BE ABLE TO SEND A LAMBDA INSTEAD OF AN OBJECT */) {
      if (trait.test(animal)) // if lambda "a -> a.canHop()" will be passed as the "trait" arg, then "trait.test(animal))" will be in fact substituted with "animal.canHop()"
         System.out.println(animal);
   }
   public static void main(String[] args) {
      Animal fish = new Animal("fish", false, true)
      // Pass a lambda expression as a parameter whose data type is the corresponding functional interface (CheckTrait).
      // The type of the lambda's argument is Animal - as the type of the parameter of the test() method of the interface. The body returns boolean like the test() method does:
      print(fish, (Animal a) -> { return a.canHop(); });
      // "a -> a.canHop()" is the short form of "(Animal a) -> { return a.canHop(); }"; .
      // Arg "a" is of type Animal (Java defines that automatically by checking the signature of the test() method). The body returns boolean like the test() method:
      print(new Animal("kangaroo", true, false), a -> a.canHop());
   }
}

// @@@ Applying the Predicate Interface:
// In our earlier example, we created a simple functional interface to test an Animal trait:
public interface CheckTrait {
   public boolean test(Animal a);
}
// You can imagine that we'd have to create lots of interfaces like this to use lambdas. We want to test animals, plants, String values, and just about anything else that we come across. Luckily, Java recognizes that this is a common problem and provides such an interface for us. It's in the package java.util.function, and the gist of it is as follows:
@FunctionalInterface
public interface Predicate<T> {
   public boolean test(T t);
}
// That looks a lot like our method. The only difference is that it uses type T instead of Animal. The result of using Predicate is that we no longer need our own functional interface. The following is a rewrite of our program to use the Predicate class:
import java.util.function.Predicate;
public class FindMatchingAnimals {
   private static void print(Animal animal, Predicate<Animal> trait) {
      if (trait.test(animal))
         System.out.println(animal);
   }
   public static void main(String[] args) {
      print(new Animal("fish", false, true), a -> a.canHop());
      print(new Animal("kangaroo", true, false), a -> a.canHop());
   }
}
User avatar
Ursego
Site Admin
 
Posts: 143
Joined: 19 Feb 2013, 20:33



Ketones are a more high-octane fuel for your brain than glucose. Become a biohacker and upgrade yourself to version 2.0!



cron
Traffic Counter

eXTReMe Tracker