DEV Community

Cover image for Functional Interfaces, Lambdas and Generics, oh my!
Layla
Layla

Posted on • Edited on

Functional Interfaces, Lambdas and Generics, oh my!

So what is all this then?

A colleague of mine, after showing him some of what I had written, seemed befuddled by all that was going on. "What is all this mess? The heck? 'Functional interface'? Java is an object-oriented language you know'. Well, that is true for the most part, but well, sometimes you just want to write some damn functionality, and so in Java 8, Oracle introduced some functional programming into what was (and is) a language known for being rigidly object-oriented. Lambdas are very commonly used in the Streams API, among others, but you can build your own! Very useful, to be sure, as they can make your code much more streamlined and svelte.

Okay, sunshine, so what is a Lambda exactly?

I'm glad you asked! So a lambda, in computer science, is essentially an "anonymous function". All functionality, no preamble, lambdas are simple, short and disposable. They have their origin in the Functional programming world, but most languages implement them in some form or another nowadays. They can be assigned to variables or used as arguments, which allows for sleek, reusable code.

In Java, one can express them as follows:

argument -> argument.startsWith("Ms");
(String argument) -> { return argument.startsWith("Ms") };
Enter fullscreen mode Exit fullscreen mode

Okay, that seems cool, but what are generics?

So generics are parameterized types. What is a parameterized type? Well, it means that the type is itself an argument of sorts. This means that one does not have to specify the specific type of an object to build something and that one only has to specify on initialization or invocation what that type is. But what does that mean, exactly? Alright, so allow me to illustrate using a simple object that simply stores another object... A box, if you will.

public class Box<T> {

  private T content;

  public Box(T content){
    this.content = content;
  }

  public void set(T content){
    this.content = content;
  }

  public T get(){
    return content;
  }
}
Enter fullscreen mode Exit fullscreen mode

What the heck? What is that mysterious 'T' that is used everywhere? Well you see, that is the generic. It is there to indicate a malleable type. The letter can be any letter. I could change it to 'A' for argument if I felt like it, but T for type is commonly handled. Other letters you might see used are S, U, V (2nd, 3rd, 4th argument and so forth), R (return type), E (element), K (key), and V (Value). Now, what is this supposed to do? Well it means that when we create a new Box, we specify the type and all the Ts in the Box will conform to that type. For example if we now create a Box as follows:

Box<Integer> box = new Box<>(5);
Enter fullscreen mode Exit fullscreen mode

(starting from Java 7, one can leave the second pair of diamond brackets empty as long as the left side is filled in.)

The box will, essentially, turn into something like this:

public class Box {

  private Integer content;

  public Box(Integer content){
    this.content = content;
  }

  public void set(Integer content){
    this.content = content;
  }

  public Integer get(){
    return content;
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, this allows for more malleable configurations. Generics can also be used in methods, which I will go into further down.

Cool, so lets put it all together! How can I make my own lambdas!

Yes! Let's get to it. To build our own lambda functions, first we have to start with a functional interface. What is a functional interface? Well, it's an interface with only one abstract method in it! This is known as the Single Abstract Method (SAM) principle. Any interface with a single abstract method qualifies as a functional interface. One can even define default or private methods in the interface or have static variables! As long as there is only one abstract method, you're good.

Lets make a lambda function that tests whether something is true. It returns a boolean but can take any sort of argument. I will call it... TruthChecker.

@FunctionalInterface
public interface TruthChecker<T> {
    boolean test(T t);
}
Enter fullscreen mode Exit fullscreen mode

(The annotation is optional but highly recommended. It functions like @Override in the sense that it clearly communicates what it is and checks whether it meets all criteria.)

Alright, so here we have it. Pretty simple right? Okay so... what now? How do I actually implement this thing?

Lets make a main somewhere, and implement the function! Here we go!

    public static void main(String[] args) {

        TruthChecker<String> checker = 
          argument -> argument.startsWith("Ms");

        System.out.println(checker.test("Mrs. E")); //false
        System.out.println(checker.test("Mr. E")); //false
        System.out.println(checker.test("Ms Demeanour")); //true
  }
Enter fullscreen mode Exit fullscreen mode

Interesting, right? we can just... use the test method inside the class. The method will correspond to the functionality we passed in! We can create multiple instances of the TruthChecker with different types and functionality, as long as it still returns a boolean, it will work:

    public static void main(String[] args) {

        TruthChecker<Integer> checker = 
          argument -> argument > 5;

        System.out.println(checker.test(5)); //false
        System.out.println(checker.test(10)); //true
  }
Enter fullscreen mode Exit fullscreen mode

Of course, savvy Java users will have cottoned onto the fact that I simply recreated the Predicate functional interface for illustration. Now I hear some of you ask: "what is a Predicate?". Well, A predicate is a type of function that returns a boolean, and in Java, a predicate is one of several prefab functional interfaces that one has access to! You don't need to build your own. Most of the time there is already a functional interface out there in the base language that you can implement!

For example, instead of our TruthChecker, we could simple do:

    public static void main(String[] args) {

        Predicate<Integer> checker = 
          argument -> argument > 5;

        System.out.println(checker.test(5)); //false
        System.out.println(checker.test(10)); //true
  }
Enter fullscreen mode Exit fullscreen mode

It even has a test function, like our TruthChecker, and more functionality besides!

Of course, being variables, some of their real power comes from being used as arguments! Observe:

    private static <T> boolean check(T t, Predicate<T> checker){
        return checker.test(t);
    }
Enter fullscreen mode Exit fullscreen mode

Using the power of generics, I have created a generalized and reusable check function! Here you can see the use of generics in methods in action. I can pass in a variable and functionality, and then the method will make use of the functionality passed in.

Lets return to our main to demonstrate:

    public static void main(String[] args) {

      System.out.println(check(5, integer -> integer > 5)); 
//false
      System.out.println(check(10, integer -> integer > 5));
 //true
      System.out.println(check("Ms de Mineur", 
        string -> string.startsWith("Ms"))); 
//true
    }
Enter fullscreen mode Exit fullscreen mode

Witchcraft! I use a single method to check various different types of objects! But this is the power of lamdas. Being variables, they can be passed as arguments. This also means one can be a return value!

    private static Function<Integer, Integer> multiplication(Function<Integer, Integer> func, Integer number){
        return integer -> integer * func.apply(number);
    }

Enter fullscreen mode Exit fullscreen mode

Now things are getting a bit funny, but stick with me. In this function, I am using the Function functional interface, which takes an argument and returns a value. The value I am returning, however, is another Function, which makes use of the result of the function I passed in!

Lets return to our main for a moment:

    public static void main(String[] args) {

        Function<Integer, Integer> multiplier = multiplication(number -> number * 5, 5); //A function that stores 5 * 5 = 25; 

        System.out.println(multiplier.apply(5)); //5*25 = 125
    }
Enter fullscreen mode Exit fullscreen mode

As you can see, this allows me to chain functionality into other functionality! The initial argument is loaded into a function and then functionality is returned which uses the result as an argument. Passing another argument into this returned functionality finally returns a value.

That was it for now! I hope that this has helped you better understand lambdas and generics in Java and how to apply them! They are a powerful tool that will allow you to write succinct, reusable and malleable code without losing type safety. Write something beautiful!

Top comments (0)