8

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]?

Community
  • 1
  • 1
Andrejs
  • 10,803
  • 4
  • 43
  • 48
  • 1
    Well ... You can pass a lambda expression (or an appropriate method reference) to the method `printPersons`. But can you easily implement the method's behavior with it? Example: `roster.stream().filter(tester).forEach(System.out::println)` – Seelenvirtuose Mar 27 '17 at 15:40
  • 1
    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. – M. Prokhorov Mar 27 '17 at 15:41
  • @khelwood: "so that I don't have to write my own interface every time I want a predicate" - I came to realize that, which is nice, of course, but I somehow thought there would be more :) – Andrejs Mar 27 '17 at 15:42
  • 2
    Accepting and implementing standard interfaces makes re-usability more likely. – Andy Thomas Mar 27 '17 at 15:42
  • 1
    Most builtin interfaces also define some other API. For example, `Predicate` defines `or(Predicate)`, `and(Predicate)`, `negate()`. `Function` defines `andThen` and `compose`, 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. – M. Prokhorov Mar 27 '17 at 15:44
  • @Seelenvirtuose: my understanding that complex implementations can then have their separate factories, of course :) as explained e.g. here https://dzone.com/articles/writing-clean-predicates-java – Andrejs Mar 27 '17 at 15:45
  • @Prokhorov: I think your comments together can be put together into an answer by now, I'll accept – Andrejs Mar 27 '17 at 15:47
  • 1
    @M.Prokhorov I'll have to agree here... those comments do count as an answer(which ill be happy to up-vote) – Eugene Mar 27 '17 at 16:09
  • 1
    Looking at the title of your question, it seems you are confusing builtin interfaces with “Functional Interfaces” in general. Your custom interface is also a function interface. So are you really asking about the “Purpose of Functional Interfaces” as your question’s title suggests or are you asking why the JRE provides builtin general purpose interfaces? – Holger Mar 27 '17 at 16:21

3 Answers3

7

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;
  }
}
M. Prokhorov
  • 3,894
  • 25
  • 39
6

Although you ask "Is that it?", it's very nice that we don't have to write a new interface ever time we want type for a lambda.

Ask yourself, if you're reading an API, which is easier for a programmer to use:

public void processUsers(UserProcessor userProcessor);

... or ...

public void processUsers(Consumer<User> userProcessor);

With the former, I have to go and take a look at UserProcessor to find out what one is, and how I could create one; I don't even know it could be implemented as a lambda until I go and find out. With the latter, I know immediately that I can type u -> System.out.println(u) and I'll be processing users by writing them to stdout.

Also the author of the library didn't need to bloat their library with Yet Another Type.

In addition, if I coerce a lambda to a Functional Type, I can use that type's composition methods, for example:

 candidates.filter( personPredicates.IS_GRADUATE.negate());

That gives you Predicate methods and(), or(), negate(); Function methods compose(), andThen() -- which your custom type would not have unless you implemented them.

slim
  • 40,215
  • 13
  • 94
  • 127
  • 2
    While I agree that JDK builtins help a lot by building a common vocabulary for us to communicate, I also think that some examples of custom interfaces actually work pretty nice. For example, a `Callback` would say a lot more to me than `Consumer` would. It's not universal, of course. That, and there's no builtin interfaces with arity more than 2 (and arity of 2 has pretty weird selection). – M. Prokhorov Mar 27 '17 at 16:31
1

Java API provides many built-in Function Interfaces for java developers. and we can use the built-in Function Interfaces many times. but there two reasons to use a Customer Function Interface.

  1. Use a Customer Function Interface to describe explicitly what's like.

    let's say you having a class User with a name parameter on the constructor.when you use the built-in Function Interface to refer the constructor the code like below:

    Function<String,User> userFactory=User::new;
    

    if you want describe it clearly you can introduce your own Function Interface, e.g:UserFactory;

    UserFactory userFactory=User::new;
    

    another reason to use Custom Function Interface due to built-in Function Interface is confused in somewhere. when you see a parameter with type Function<String,User>,is it create a new user or query an user from database or remove the user by a string and return the user,...?if you use an exactly Function Interface you know what it doing,as an UserFactory is create an user from a string username.

  2. Use a Customer Function Interface to processing checked Exception in java built-in Function Interface.

    Due to the built-in Function Interface can't be throwing a checked Exception,the problem occurs when you processing something in lambda expression that may be throws a checked exception,but you don't want to use the try/catch to handle the checked Exception,which will be tends to many code difficult to read in lambda expression.then you can define your own Function Interface that throws any CheckedException and adapt it to the built-in Function Interface when using java API.the code like as below:

    //if the bars function throws a checked Exception,you must catch the exception
    Stream.of(foos).map(t->{
       try{
          return bars.apply(t);
       }catch(ex){//handle exception}
    });
    

    you also can define your own Function Interface throws a checked Exception,then adapt it to built-in Function,let's say it is a Mapping interface,the code below:

    interface Mapping<T,R> {
      R apply(T value) throws Exception;
    }
    
    private Function<T,R> reportsErrorWhenMappingFailed(Mapping<T,R> mapping){
      return (it)->{
        try{
          return mapping.apply(it);
        }catch(ex){
          handleException(ex);
          return null;
        }
      };
    } 
    Stream.of(foos).map(reportsErrorWhenMappingFailed(bars));
    
holi-java
  • 29,655
  • 7
  • 72
  • 83