java8 – Functional Interfaces

2019-08-01  本文已影响0人  bern85

什么是Functional interfaces

Functional interfaces 也被称作Single Abstract Method interfaces (SAM Interfaces). 顾名思义,它们有且只有一个抽象方法. Java 8引入了一个注释,即@FunctionalInterface,当你使用@FunctionalInterface注释的接口违反了Functional Interface的规定时,编译器将会报错。

在Java 8中,Functional interfaces也可以使用lambda表达式,方法引用和构造函数引用来表示。

一个典型的Functional Interface示例如下:

@FunctionalInterface
public interface MyFirstFunctionalInterface {
    public void firstWork();
}

我们试着再加入一个抽象方法:

@FunctionalInterface
public interface MyFirstFunctionalInterface
{
    public void firstWork();
    public void doSomeMoreWork();   //error
}

编译器报错:
D:\workspace\ideaws\test\java8\src\main\java\com\ancs\java8\MyFirstFunctionalInterface.java
Error:Error:line (3)java: 意外的 @FunctionalInterface 注释
com.ancs.java8.MyFirstFunctionalInterface 不是函数接口
在 接口 com.ancs.java8.MyFirstFunctionalInterface 中找到多个非覆盖抽象方法


Note: 即使省略@FunctionalInterface注释,Functional Interface也是有效的。 注释的作用仅仅是告诉编译器,检查接口是否只有一个抽象方法。

此外,由于java8 引入了默认方法,默认方法不属于抽象方法,所以你可以根据需要在Functional Interface随意增加默认默认方法。如下所示:

@FunctionalInterface
public interface MyFirstFunctionalInterface
{
    public void firstWork();
 
    default void doSomeMoreWork1(){
    //Method body
    }
 
    default void doSomeMoreWork2(){
    //Method body
    }
}

另外需要注意的是,如果接口声明的抽象方法来自java.lang.Object,那么它也不会计入抽象方法的计数。因为任何接口的实现都默认继承自java.lang.Object。例如,下面的Functional Interface也是有效的:

@FunctionalInterface
public interface MyFirstFunctionalInterface
{
    public void firstWork();
 
    @Override
    public String toString();                //Overridden from Object class
 
    @Override
    public boolean equals(Object obj);        //Overridden from Object class
}

java8 中常用函数式接口

Java API中已经有了几个函数式接口,比如Comparable、Runnable和 Callable。 而且再java.util.function包中引入了几个新的函数式接口。

Note : (T,U) -> R的表达方式展示了应当如何思考 一个函数描述符。表的左侧代表了参数类型。这里它代表一个函数,具有两个参数,分别为泛型 T和U,返回类型为R。

Java API中提供的常用的函数式接口及其函数描述符列表如下:

函数式接口 函数描述符 原始类型特化
Predicate<T> T->boolean IntPredicate
LongPredicate
DoublePredicate
Consumer<T> T->void IntConsumer
LongConsumer
DoubleConsumer
Function<T,R> T->R IntFunction<R>
IntToDoubleFunction
IntToLongFunction
LongFunction<R>
LongToDoubleFunction
LongToIntFunction
DoubleFunction<R>
ToIntFunction<T>
ToDoubleFunction<T>
ToLongFunction<T>
Supplier<T> ()->T BooleanSupplier
IntSupplier
LongSupplier
DoubleSupplier
UnaryOperator<T> T->T IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
BinaryOperator<T> (T,T)->T IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator
BiPredicate<L,R> (L,R)->boolean
BiConsumer<T,U> (T,U)->void ObjIntConsumer<T>
ObjLongConsumer<T>
ObjDoubleConsumer<T>
BiFunction<T,U,R> (T,U)->R ToIntBiFunction<T,U>
ToLongBiFunction<T,U>
ToDoubleBiFunction<T,U>

我们接下来会介绍Predicate、Consumer和Function.

Predicate

在数学中, predicate 通常被理解为布尔值函数' 'P: X? {true, false}', 称之为 predicate on X. 它可以被认为是一个返回值为true或false的运算符或函数.
在Java 8中, Predicate 是一个 functional interface ,因此可以用作赋值给 lambda expression 或者方法引用. 那么你认为我们会在日常编程当中使用这些返回true/false得函数么?我可以肯定得告诉你,你可以使用predicates在你需要判断任何组/集合中的对象是 true/false 的地方使用.
例如,您可以在这些实时用例中使用Predicate:

  1. 查找特定日期之后出生的所有孩子
  2. 查找特定日期订购的披萨订单
  3. 超过特定年龄的员工等等
    所以Predicate似乎是一个很有意思的类,我们接下来深入了解一下。
    正如我们所说的那样, Predicate是一个functional interface. 这意味着我们可以在需要使用predicate的地方使用lambda 表达式. 例如 Stream 接口中的filter() 方法.
/**
 * Returns a stream consisting of the elements of this stream that match
 * the given predicate.
 *
 * <p>This is an <a href="package-summary.html#StreamOps">intermediate
 * operation</a>.
 *
 * @param predicate a non-interfering stateless predicate to apply to each element to determine if it
 * should be included in the new returned stream.
 * @return the new stream
 */
Stream<T> filter(Predicate<? super T> predicate);

我们可以把stream看作一个可以用来创建支持串行和并行聚合操作的元素序列的机制。就是说,我们可以随时对流中的元素做一些操作.
所以我们可以使用streampredicate做如下操作:

在集合中使用Predicate

为了演示,我们有一个Employee类,如下所示:

package predicateExample;

public class Employee {

    public Employee(Integer id, Integer age, String gender, String fName, String lName){
        this.id = id;
        this.age = age;
        this.gender = gender;
        this.firstName = fName;
        this.lastName = lName;
    }

    private Integer id;
    private Integer age;
    private String gender;
    private String firstName;
    private String lastName;

    //Please generate Getter and Setters

    //To change body of generated methods, choose Tools | Templates.
    @Override
    public String toString() {
        return this.id.toString()+" - "+this.age.toString();
    }
}
1. 查找所有年龄超过21岁的男性员工
public static Predicate<Employee> isAdultMale()
{
    return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
}
2. 查找所有年龄超过18岁的女性员工
public static Predicate<Employee> isAdultFemale()
{
    return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
}
3. 查找超过给定年龄的员工
public static Predicate<Employee> isAgeMoreThan(Integer age)
{
    return p -> p.getAge() > age;
}

你可以根据需求创建更多的Predicate,EmployeePredicates.java包含了上面的三个方法,如下所示:

package predicateExample;
 
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
 
public class EmployeePredicates
{
    public static Predicate<Employee> isAdultMale() {
        return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
    }
     
    public static Predicate<Employee> isAdultFemale() {
        return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
    }
     
    public static Predicate<Employee> isAgeMoreThan(Integer age) {
        return p -> p.getAge() > age;
    }
     
    public static List<Employee> filterEmployees (List<Employee> employees,
                                                Predicate<Employee> predicate)
    {
        return employees.stream()
                    .filter( predicate )
                    .collect(Collectors.<Employee>toList());
    }
}  

我写了一个 filterEmployees()方法来说明 predicate filter的使用方式. 它使得代码看起来更简洁,重复性更低. 你可以可以创建多个 predicate 链, 类似于 builder设计模式.
filterEmployees()方法中有两个参数,List<Employee>Predicate<Employee> ,返回满足Predicate条件的一个新的Employee集合.
下面是测试类TestEmployeePredicates.java,如下所示:

package predicateExample;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static predicateExample.EmployeePredicates.*;
 
public class TestEmployeePredicates
{
    public static void main(String[] args)
    {
        Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
        Employee e2 = new Employee(2,13,"F","Martina","Hengis");
        Employee e3 = new Employee(3,43,"M","Ricky","Martin");
        Employee e4 = new Employee(4,26,"M","Jon","Lowman");
        Employee e5 = new Employee(5,19,"F","Cristine","Maria");
        Employee e6 = new Employee(6,15,"M","David","Feezor");
        Employee e7 = new Employee(7,68,"F","Melissa","Roy");
        Employee e8 = new Employee(8,79,"M","Alex","Gussin");
        Employee e9 = new Employee(9,15,"F","Neetu","Singh");
        Employee e10 = new Employee(10,45,"M","Naveen","Jain");
         
        List<Employee> employees = new ArrayList<Employee>();
        employees.addAll(Arrays.asList(new Employee[]{e1,e2,e3,e4,e5,e6,e7,e8,e9,e10}));
                
        System.out.println( filterEmployees(employees, isAdultMale()) );
         
        System.out.println( filterEmployees(employees, isAdultFemale()) );
         
        System.out.println( filterEmployees(employees, isAgeMoreThan(35)) );
         
        //Employees other than above collection of "isAgeMoreThan(35)"
        //can be get using negate()
        System.out.println(filterEmployees(employees, isAgeMoreThan(35).negate()));
    }
}
 
Output:
 
[1 - 23, 3 - 43, 4 - 26, 8 - 79, 10 - 45]
[5 - 19, 7 - 68]
[3 - 43, 7 - 68, 8 - 79, 10 - 45]
[1 - 23, 2 - 13, 4 - 26, 5 - 19, 6 - 15, 9 - 15]

Predicates是java 8中一个非常好的新工具类,每当有使用场景时,我都会用到它。

Consumer

java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象参数,没有返回值(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用 这个接口。比如,你可以用它来创建一个forEach方法,接受一个Integers的列表,并对其中 每个元素执行操作。你可以使用这个forEach方法,并配合Lambda来打印列表中的所有元素。示例代码TestConsumer 如下所示:

package consumerExample;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class TestConsumer {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Larry", "Steve", "James");

        //use lambda expression
        System.out.println("Consumer test with lambda expression");
        Consumer<String> printConsumer = s -> System.out.println(s);
        names.forEach(printConsumer);

        // use method reference
        System.out.println("Consumer test with method reference");
        names.forEach(System.out::println);
    }
}

输出如下:

Consumer test with lambda expression
Larry
Steve
James
Consumer test with method reference
Larry
Steve
James

ConsumerPredicate更容易理解,它也是java 8 中新增的工具类。

Function

java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个 泛型T的对象,并返回一个泛型R的对象。

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

如果你需要定义一个Lambda,将输入对象的信息映射 到输出,就可以使用这个接口。在下面的代码中,我们向你展示如何利用它来创建一个map方法,以将一个String列表映射到包含每个String长度的Integer列表。 TestFunction如下所示:

package functionExample;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

public class TestFunction {
    public static void main(String[] args) {
        List<Integer> result = map(Arrays.asList("lambdas","in","action"),
                (String s) -> s.length()
        );
        System.out.println(result);
    }
    public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
        List<R> result = new ArrayList<>();
        for(T s: list){
            result.add(f.apply(s));
        }
        return result;
    }
}

输出如下:

[7, 2, 6]

由于本章节主要介绍Function Interfaces,所以例如Function的其他三个default 方法:addThen(),compose(),identity()就不在此详细论述了。大家可以参考TestFunctionAndThen,TestFunctionComposeTestFunctionIdentity.

原始类型特化

Java类型要么是引用类型(比如Byte、Integer、Object、List),要么是原 始类型(比如int、double、byte、char)。但是泛型(比如Consumer<T>中的T)只能绑定到 引用类型。这是由泛型内部的实现方式造成的。因此,在Java里有一个将原始类型转换为对应 的引用类型的机制。这个机制叫作装箱(boxing)。相反的操作,也就是将引用类型转换为对应的原始类型,叫作拆箱(unboxing)。Java还有一个自动装箱机制来帮助程序员执行这一任务:装 箱和拆箱操作是自动完成的。比如,这就是为什么下面的代码是有效的(一个int被装箱成为 Integer):

List<Integer> list = new ArrayList<>(); 
for (int i = 300; i < 400; i++)
{    
    list.add(i);
} 

但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆 里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
Java 8为我们前面所说的函数式接口带来了一个专门的版本,以便在输入和输出都是原始类 型时避免自动装箱的操作。比如,在下面的代码中,使用IntPredicate就避免了对值1000进行 装箱操作,但要是用Predicate<Integer>就会把参数1000装箱到一个Integer对象中:

// true(无装箱)
IntPredicate evenNumbers = (int i) -> i % 2 == 0; 
evenNumbers.test(1000); 
//false(装箱)
Predicate<Integer> oddNumbers = (Integer i) -> i % 2 == 1; 
oddNumbers.test(1000); 

一般来说,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前,比 如DoublePredicate、IntConsumer、LongBinaryOperator、IntFunction等。Function 接口还有针对输出参数类型的变种:ToIntFunction<T>、IntToDoubleFunction等。

上一篇下一篇

猜你喜欢

热点阅读