Lambda与函数式接口

JDK8 于2014年发布至今,已有6个年头。其中几个重要的特性,lambda表达式,函数式接口,方法引用,默认方法等,至今未完全普及。在回到家乡邯郸这个三线小城市后,竟然有同事问我,有没有简单的统计List数据的方法,颇让我惊讶。虽然我认为一个对公司有贡献的程序员,首先应该熟悉业务。但是我也担心,技术上落后太多,也会拖累项目的性能。因此,本着服务家乡的想法,梳理一下Java程序员基本的知识点。与互联网项目不同,我更加重视基础知识。对于微服务框架,我认为绝大多数应用,是用不到的。如果为了使用而使用,只会增加团队不必要的负担。闲话不多说,我们开始介绍Lambda,同时,会介绍函数式接口。

引言

Lambda表达式,也可以称作闭包。它是推动Jdk8发布的最重要的特性。在此之前,jdk是不允许将函数作为参数进行传递的,即不支持函数式编程。这使其在很多地方,代码过于啰嗦,如同老太太的叨叨。因此,在Jdk8引入Lambda后,代码变得更加简洁,更加清晰了。

Lambda语法

完整的Lambda表达式包含三部分组成:参数列表,箭头,声明语句。

(Type1 param1, Type2 param2, ..., TypeN paramN)-> {声明1; 声明2; ...... return mentmentM;}

以下是Lambda表达是的重要说明:

1,参数可选类型声明:不需要指明param1,param2的具体类型,编译器可以自动识别。于是,上面可以简化为:

(param1, param2, ..., paramN)-> {指令1; 指令2; ......}

2,参数可选圆括号:当参数仅有1个的时候,圆括号可以省略。于是,上面可以继续简化为:

param-> {指令1; 指令2; ......}

3,函数体可选大括号:当函数体中仅包含一个指令的时候,大括号可以省略。

param-> 指令1;

具体表达式示例

// 1.不需要的参数,返回变量5 
() -> 5

// 2.接收一个参数(数字类型),返回其2倍的值
x -> 2 * x

// 3.接受2个参数(数字),并返回他们的差值
(x,y) -> x-y

// 4.接收2个int型整数,返回他们的和
(int x, int y) -> x + y

// 5.接受一个字符串对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s)-> System.out.print(s)

函数式接口

要想使用Lambda,必然需要一个函数式接口。那么,什么是函数式接口呢?函数式接口,Functional Interface,必须有且仅有一个抽象方法的接口,对默认方法的个数没有限制(Jdk8 允许在接口中有默认实现),同时函数式接口用@FunctionalInterface来标注。函数式接口可以友好的支持Lambda。

Jdk8 之前符合函数式接口条件的接口有:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

Jdk8 新增的函数接口一般都存储在java.util.function中,其中有很多类,这里不一一赘述。感兴趣的同学,可以在这个package中自学一下。接下来,我们举两个例子,来说明函数式接口与Lambda的用法。

public class LambdaDemo {

    public static void main(String[] args) {
        runDemo();
    }

    public static void runDemo() {
        //传统定义
        Runnable runnableJdk7 = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread());
            }
        };
        runnableJdk7.run();

        //Lambda 形式
        Runnable runnableJdk8 = () -> System.out.println(Thread.currentThread());
        runnableJdk8.run();
    }

}

上述代码,以定义一个线程为例,展示了Jdk8以前以及Jdk8的做法。那么,Lambda究竟做了什么呢?很明显,简化了接口抽象方法的实现,我们用Lambda实现了Runnablerun方法,并创建了一个对象runnableJdk8。这就是函数式接口必须有且只能有一个抽象方法的原因。因为创建的时候,等号右面必须有,且只能有一个Lambda表达式,这个Lambda表达式,就是用来实现这个函数式接口唯一的抽象方法。

我们再举一个例子,假设有一张学生成绩表,筛选成绩大于80的。该怎么写呢?

public static void score() {

        List<Student> listJdk7 = new ArrayList<>(
                Arrays.asList(
                        new Student(1, "张三", 41),
                        new Student(2, "王二", 92),
                        new Student(3, "李四", 52),
                        new Student(4, "王五", 81),
                        new Student(5, "赵六", 67)
                )
        );

        Iterator<Student> listIt = listJdk7.iterator();
        while (listIt.hasNext()) {
            Student next = listIt.next();
            if (next.getScore().intValue() < 80) {
                listIt.remove();
            }
        }
        System.out.println(JSON.toJSONString(listJdk7, SerializerFeature.PrettyFormat));
                
            //以下是Jdk8 Lambda 结合 函数式接口
        List<Student> listJdk8 = new ArrayList<>(
                Arrays.asList(
                        new Student(1, "张三", 41),
                        new Student(2, "王二", 92),
                        new Student(3, "李四", 52),
                        new Student(4, "王五", 81),
                        new Student(5, "赵六", 67)
                )
        );
        List<Student> collect = listJdk8.stream().filter(x -> x.getScore() > 80).collect(Collectors.toList());
        System.out.println(JSON.toJSONString(collect, SerializerFeature.PrettyFormat));
    }

Jdk7这里不再解释,这里解释一下Jdk8的实现。Jdk8调用了Stream的实现类java.util.stream.ReferencePipeline<P_IN, P_OUT>filter方法,具体实现如下。

    @Override
    public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
        Objects.requireNonNull(predicate);
        return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                     StreamOpFlag.NOT_SIZED) {
            @Override
            Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
                return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                    @Override
                    public void begin(long size) {
                        downstream.begin(-1);
                    }

                    @Override
                    public void accept(P_OUT u) {
                        if (predicate.test(u))
                            downstream.accept(u);
                    }
                };
            }
        };
    }

从实现中,我们可以看到,这个方法,传入一个java.util.function.Predicate的接口函数,并且调用了其抽象方法test(T t)。该接口的内容大致如下,其中抽象方法test,接受一个输入对象,返回ture或者false。额,我觉得这有点废话,毕竟代码是这么写的。(#.#)

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
        
        ...
}

我们刚才的写法是

x -> x.getScore() > 80

x 代表我们要操作的对象,也就是test方法的入参t。而x.getScore()>80则是test方法的具体实现。这个实现会返回boolean值。这样是不是好理解了。

总结

借助函数式接口与接口默认方法,可以使代码变得更简洁,更快速。

那么,要来一杯咖啡吗?

附录:Jdk8 自带的函数式接口

序号 接口 & 描述
1 BiConsumer<T,U>代表了一个接受两个输入参数的操作,并且不返回任何结果
2 BiFunction<T,U,R>代表了一个接受两个输入参数的方法,并且返回一个结果
3 BinaryOperator<T>代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
4 BiPredicate<T,U>代表了一个两个参数的boolean值方法
5 BooleanSupplier代表了boolean值结果的提供方
6 Consumer<T>代表了接受一个输入参数并且无返回的操作
7 DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
8 DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。
9 DoubleFunction<R>代表接受一个double值参数的方法,并且返回结果
10 DoublePredicate代表一个拥有double值参数的boolean值方法
11 DoubleSupplier代表一个double值结构的提供方
12 DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。
13 DoubleToLongFunction接受一个double类型输入,返回一个long类型结果
14 DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。
15 Function<T,R>接受一个输入参数,返回一个结果。
16 IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。
17 IntConsumer接受一个int类型的输入参数,无返回值 。
18 IntFunction<R>接受一个int类型输入参数,返回一个结果 。
19 IntPredicate:接受一个int输入参数,返回一个布尔值的结果。
20 IntSupplier无参数,返回一个int类型结果。
21 IntToDoubleFunction接受一个int类型输入,返回一个double类型结果 。
22 IntToLongFunction接受一个int类型输入,返回一个long类型结果。
23 IntUnaryOperator接受一个参数同为类型int,返回值类型也为int 。
24 LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。
25 LongConsumer接受一个long类型的输入参数,无返回值。
26 LongFunction<R>接受一个long类型输入参数,返回一个结果。
27 LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。
28 LongSupplier无参数,返回一个结果long类型的值。
29 LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。
30 LongToIntFunction接受一个long类型输入,返回一个int类型结果。
31 LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。
32 ObjDoubleConsumer<T>接受一个object类型和一个double类型的输入参数,无返回值。
33 ObjIntConsumer<T>接受一个object类型和一个int类型的输入参数,无返回值。
34 ObjLongConsumer<T>接受一个object类型和一个long类型的输入参数,无返回值。
35 Predicate<T>接受一个输入参数,返回一个布尔值结果。
36 Supplier<T>无参数,返回一个结果。
37 ToDoubleBiFunction<T,U>接受两个输入参数,返回一个double类型结果
38 ToDoubleFunction<T>接受一个输入参数,返回一个double类型结果
39 ToIntBiFunction<T,U>接受两个输入参数,返回一个int类型结果。
40 ToIntFunction<T>接受一个输入参数,返回一个int类型结果。
41 ToLongBiFunction<T,U>接受两个输入参数,返回一个long类型结果。
42 ToLongFunction<T>接受一个输入参数,返回一个long类型结果。
43 UnaryOperator<T>接受一个参数为类型T,返回值类型也为T。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容