java8 – Functional Interfaces

什么是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等。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容