Java8 中功能接口的用途

Purpose of Functional Interfaces in Java8

我遇到过很多关于 Java8 内置 功能接口的问题,包括 , this, and 。但所有人都会询问 "why only one method?" 或 "why do I get a compilation error if I do X with my functional interface" 之类的。我的问题是:当我可以在自己的接口中使用 lambda 时,这些新功能接口的存在目的是什么?

考虑以下来自 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);
                }
            }
        }

好的,很好,但这可以通过上面他们自己的示例来实现(具有单一方法的接口并不是什么新鲜事):

      // 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);
    }

我可以将 lambda 传递给这两种方法。

第一种方法为我节省了一个自定义界面。 是这个吗

或者这些标准功能接口(Consumer、Supplier、Predicate、Function)是否旨在作为代码组织、可读性、结构、[其他]的模板?

尽管您问 "Is that it?",但我们不必在每次需要 lambda 类型时都编写新接口,这非常好。

问问自己,如果您正在阅读 API,程序员更容易使用:

public void processUsers(UserProcessor userProcessor);

...或...

public void processUsers(Consumer<User> userProcessor);

对于前者,我必须去看看UserProcessor,看看它是什么,以及如何创建它;在我去发现之前,我什至不知道它可以作为 lambda 实现。对于后者,我立即知道我可以输入 u -> System.out.println(u) 并且我将通过将用户写入标准输出来处理用户。

另外 图书馆的作者不需要用另一种类型来膨胀他们的图书馆。

此外,如果我将 lambda 强制转换为函数类型,我可以使用该类型的组合方法,例如:

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

这为您提供 Predicate 方法 and()or()negate()Function 方法 compose()andThen() -- 除非您实现它们,否则您的自定义类型不会有这些方法。

显然,您可以跳过使用这些新界面并使用更好的名称滚动您自己的界面。不过有一些注意事项:

  1. 您将无法在其他一些 JDK API 中使用自定义界面,除非您的自定义界面扩展了其中一个内置项。
  2. 如果你总是随心所欲,在某些时候你会遇到想不出好名字的情况。例如,我认为 CheckPerson 就其目的而言并不是一个好名字,尽管这是主观的。

大多数内置接口还定义了其他一些 API。例如,Predicate 定义了 or(Predicate)and(Predicate)negate()

Function定义andThen(Function)compose(Function)

它不是特别令人兴奋,直到它是:在函数上使用抽象方法以外的方法可以更轻松地组合、策略选择等等,例如(使用 this article 中建议的样式):

之前:

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

可能会变成这样,最终可重复使用:

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;
  }
}

Java API 为 java 开发人员提供了许多内置函数接口。我们可以多次使用内置的函数接口。但是使用客户功能接口有两个原因。

  1. 使用客户功能接口来明确描述它是什么样的。

    假设您有一个 class User,在 constructor.when 上有一个名称参数,您使用内置函数接口来引用构造函数,代码如下:

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

    如果想描述清楚可以引入自己的Function Interface,例如:UserFactory;

    UserFactory userFactory=User::new;
    

    由于内置函数接口而使用自定义函数接口的另一个原因在某处令人困惑。当您看到类型为 Function<String,User> 的参数时,它是创建新用户还是从数据库中查询用户或通过字符串删除用户和 return 用户,...?函数接口你知道它在做什么,因为 UserFactory 是从字符串用户名创建用户。

  2. 使用客户函数接口处理 java 内置函数接口中检查的异常。

    由于内置函数接口不能抛出检查异常,当你在lambda表达式中处理可能抛出检查异常的东西时出现问题,但你不想使用try/catch 来处理已检查的异常,这将导致许多代码在 lambda 中难以阅读 expression.then 您可以定义自己的函数接口来抛出任何 CheckedException 并在使用时将其适应内置函数接口java API.the 代码如下:

    //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}
    });
    

    你也可以定义自己的Function Interface抛出checked Exception,然后适配内置Function,假设是Mapping接口,代码如下:

    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));