Search code examples
javajava-8functional-interface

Purpose of Functional Interfaces in Java8


I've come across many questions in regards of Java8 in-built Functional Interfaces, including this, this, this and this. But all ask about "why only one method?" or "why do I get a compilation error if I do X with my functional interface" and alike. My question is: what is the existential purpose of these new Functional Interfaces, when I can use lambdas anyway in my own interfaces?

Consider the following example code from oracle documentation:

    // Approach 6: print using a predicate
     public static void printPersonsWithPredicate(List<Person> roster, 
                                                  Predicate<Person> tester) {
            for (Person p : roster) {
                if (tester.test(p)) {
                   System.out.println(p);
                }
            }
        }

OK, great, but this is achievable with their own example just above (an interface with a single method is nothing new):

      // Approach 5: 
        public static void printPersons(<Person> roster, 
                                        CheckPerson tester) {
            for (Person p : roster) {
                if (tester.test(p)) {
                   System.out.println(p);
                }
            }
        }


  interface CheckPerson {
        boolean test(Person p);
    }

I can pass a lambda to both methods.

1st approach saves me one custom interface. Is this it?

Or are these standard functional interfaces (Consumer, Supplier, Predicate, Function) are meant to serve as a template for code organization, readability, structure, [other]?


Solution

  • Obviously you can skip using these new interfaces and roll your own with better names. There are some considerations though:

    1. You will not be able to use custom interface in some other JDK API unless your custom interface extends one of built-ins.
    2. If you always roll with your own, at some point you will come across a case where you can't think of a good name. For example, I'd argue that CheckPerson isn't really a good name for its purpose, although that's subjective.

    Most builtin interfaces also define some other API. For example, Predicate defines or(Predicate), and(Predicate) and negate().

    Function defines andThen(Function) and compose(Function), etc.

    It's not particularly exciting, until it is: using methods other than abstract ones on functions allows for easier composition, strategy selections and many more, such as (using style suggested in this article):

    Before:

    class PersonPredicate {
      public Predicate<Person> isAdultMale() {
        return p -> 
                p.getAge() > ADULT
                && p.getSex() == SexEnum.MALE;
      }
    }
    

    Might just become this, which is more reusable in the end:

    class PersonPredicate {
      public Predicate<Person> isAdultMale() {
        return isAdult().and(isMale());
      }
    
      publci Predicate<Person> isAdultFemale() {
        return isAdult().and(isFemale());
      }
    
      public Predicate<Person> isAdult() {
        return p -> p.getAge() > ADULT;
      }
    
      public Predicate<Person> isMale() {
        return isSex(SexEnum.MALE);
      }
      public Predicate<Person> isFemale() {
        return isSex(SexEnum.FEMALE);
      }
      public Predicate<Person> isSex(SexEnum sex) {
        return p -> p.getSex() == sex;
      }
    }