Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 428
Andrejs Avatar asked Mar 27 '17 15:03

Andrejs


3 Answers

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;
  }
}
like image 93
M. Prokhorov Avatar answered Oct 21 '22 01:10

M. Prokhorov


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.

like image 31
slim Avatar answered Oct 21 '22 00:10

slim


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));
    
like image 27
holi-java Avatar answered Oct 21 '22 00:10

holi-java