【Java 8 学习笔记 (2)】 Functional Interface

上一篇Java学习笔记中给大家介绍了Stream,在Stream的操作方法中的参数都是Functional Interface(本文接下来简称FI)的实例, 所以要想用好Stream,就要弄懂FI。

What

关于FI,官方只给出了描述性的定义,即只包含一个抽象方法的接口即为函数式接口。这里呢,官方文档上,有一条额外的说明,原话如下:

If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

翻译过来大致的意思就是,FI中可以声明额外的抽象方法,但是其方法签名必须和java.lang.Object的public方法一致,因为这类任何接口都会存在一个来自java.lang.Object或者其他地方的实现(并不能称得上真正的抽象),所以这一类方法并不计入FI接口的抽象方法的个数。

当然大家可以将FI理解为接口中的一种特例。Java 8 提供了@FunctionalInterface 这个注解来方便我们定义函数式接口。比如我们常见的Runnable接口,就是一个函数式接口。

1
2
3
4
5
@FunctionalInterface
public interface Runnable {
//...
public abstract void run();
}

How

In Java 8, FI的实例可以通过lambda表达式,方法引用(Method Reference), 构造方法引用(Constructor Reference)三种方式来创建。本文将重点介绍一下lambda表达式,至于Method References,大家可以参考这篇文章.

就拿上面提到的Runnable接口为例。在Java 7 or lower,我们可能要这样写:

1
2
3
4
5
6
7
8
9
10
11
//Java 7 or lower
Runnable task1 = new Runnable()
{
@Override
public void run()
{
System.out.println("Task 1 is running...");
}
};
Thread thread1 = new Thread(task1);
thread1.start();
1
2
3
4
//Java 8
Runnable task1 = () -> System.out.println("Task 1 is running...");
Thread thread1 = new Thread(task1);
thread1.start();

从上面的两段代码,我们不难发现在Java8中,可以使用lambda表达式(或者方法引用,及构造方法引用)来替代之前Java版本中的匿名内部类。正是由于FI只有一个抽象方法,所以() -> System.out.println("Task 1 is running...");理所当然就指向了该抽象方法的实现。

lambda 表达式

lambda表达式的语法:

(args1, args2, args3, ……) -> { body }

表达式可分为参数集合与函数体2部分。参数集合可接受0个或多个参数,例如,() -> { some codes },(int a) -> { some codes }, (int a, int b, int c) -> { some codes }, 如果参数集合只有一个参数,其()可以省略;此外,由于FI接口在定义时,已经对其参数类型做过限制,因此参数类型亦可省略。
对于,函数体部分如果只有一行代码,最外层的{ }可以省略。

Java 8 内置常见的FI

Java 8 也提供了几种常见的FI,接下来结合Stream中的一些方法,给大家做个简单的介绍:

Function

Function接口表示:接受一个参数T,经过操作之后返回一个结果R。例如Stream中的map()方法:

1
2
3
4
5
6
/*
Returns a stream consisting of the results of applying the given function to the elements of this stream.
map(Function<? super T,? extends R> mapper)
*/
//Example: 从Stream<Person>流中找出每个girl的微信号,并返回Stream<String>
Stream<String> weChatStream = girls.map( girl -> girl.getWechat() );

Predicate

Predicate用来判断一个对象T是否满足某种条件,返回boolean值。

1
2
3
4
5
6
/*
Returns a stream consisting of the elements of this stream that match the given predicate.
filter(Predicate<? super T> predicate)
*/
//Example: 从Stream<Person>流中找出所有女生
Stream<Person> grils = persons.filter( p -> p.getGender() == Gender.FEMALE )

Consumer

Consumer表示对一个传入对象T进行操作,但并无返回值。

1
2
3
4
5
6
/*
Performs an action for each element of this stream.
forEach(Consumer<? super T> action)
*/
//Example: 打印每个女生的微信号
grils.forEach( (girl) -> System.out.println( girl.getWeChat()) );

Supplier

Supplier表示返回一个T类型的数据对象.

1
2
3
4
5
6
/*
Returns an infinite sequential unordered stream where each element is generated by the provided Supplier.
generate(Supplier<T> s)
*/
//Example: 生成一个无限的随机数流
Stream.generate( () -> Math.random() );

小结

通过本文,大家可以发现,为了支持函数式编程,Java“不计代价”的引入了Functional Interface,Default Method,Method Reference等一些全新的概念,可谓用心良苦。这两篇文章的目的也就在于给大家做一个概述,要想深入了解还需要在实际应用中慢慢摸索。本文如有不当之处,欢迎大家批评指正。

路漫漫其修远兮,吾将上下而求索。。。

参考文献

  1. The Java™ Tutorials - Lambda Expressions
  2. Java8学习笔记(1) – 从函数式接口说起
  3. JDK 8 Doc