Java8新特性学习--函数式编程

本文深入探讨Java8中函数式编程的概念与实践,讲解Lambda表达式、函数式接口及预定义函数式接口如Consumer、Function和Predicate的使用,展示其在代码简洁性和并行处理方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是函数式编程

函数式编程并不是Java新提出的概念,其与指令编程相比,强调函数的计算比指令的计算更重要;与过程化编程相比,其中函数的计算可以随时调用。

当然,大家应该都知道面向对象的特性(抽象、封装、继承、多态)。其实在Java8出现之前,我们关注的往往是某一类对象应该具有什么样的属性,当然这也是面向对象的核心--对数据进行抽象。

但是java8出现以后,这一点开始出现变化,似乎在某种场景下,更加关注某一类共有的行为(这似乎与之前的接口有些类似),这也就是java8提出函数式编程的目的。如图所示,展示了面向对象编程到面向行为编程的变化。

640?wx_fmt=png

Java8新引入函数式编程方式,大大的提高了编码效率。

lambda表达式

为什么需要Lambda表达式?首先,不得不提增加Lambda的目的,其实就是为了支持函数式编程,而为了支持Lambda表达式,才有了函数式接口。另外,为了在面对大型数据集合时,为了能够更加高效的开发,编写的代码更加易于维护,更加容易运行在多核CPU上,java在语言层面增加了Lambda表达式。

lambda表达式即匿名函数,它是一段没有函数名的函数体,可以作为参数直接传递给相关调用者。

Lambda 表达式通常使用 (argument) -> (body) 语法书写,例如:

(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }

举例来说:

(int a, int b) -> {  return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 }

Lambda 表达式的结构:

1. 一个 Lambda 表达式可以有零个或多个参数

2. 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同

3. 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)

4. 空圆括号代表参数集为空。例如:() -> 42

5. 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a

6. Lambda 表达式的主体可包含零条或多条语句

7. 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致

8. 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空

函数式接口

关于接口的变动,Java8中新定义了一种接口类型,函数式接口,与其他接口的区别就是:

1. 函数式接口中只能有一个抽象方法(我们在这里不包括与Object的方法重名的方法);

2. 可以有从Object继承过来的抽象方法,因为所有类的最终父类都是Object;

3. 接口中唯一抽象方法的命名并不重要,因为函数式接口就是对某一行为进行抽象,主要目的就是支持Lambda表达式。

一般通过@FunctionalInterface这个注解来表明某个接口是一个函数式接口,虽然这个注解的使用不是强制性的,但是使用它的好处是让此接口的目的更加明确,同时编译器也会对代码进行检查,来确保被该注解标注的接口的使用没有语法错误。函数式接口是Java支持函数式编程的基础。

Java8函数式编程语法入门

Java8中函数式编程语法能够精简代码。 

使用Consumer作为示例,它是一个java中的预先定义的函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出。 

@FunctionalInterface //指定为函数式接口
public interface Consumer < T > {

    void accept(T t);
    // 注意函数式接口只能有一个抽象函数,如果多于1个就会报错
//  void accept2(T t); 

    // default是java8另外一个新特性,默认实现函数,所以不是抽象的,不会报错
    // 下面我们再讲一下这个函数
    default Consumer< T > andThen(Consumer< ? super T > after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

}

为什么需要default method?

1. 即使你的API已经发布出去了,你依然可以为接口添加新方法并且无需考虑向后兼容问题。

2. java8 对Lambda的支持必然会影响JDK API的接口,如果直接在接口中添加方法,就会导致所有实现该接口的类或者接口无法通过编译。

现在我们要定义一个Consumer对象,传统的方式是这样定义的:

Consumer c = new Consumer() {
    @Override
    public void accept(Object o) {
        System.out.println(o);
    }
};

而在Java8中,针对函数式编程接口,可以这样定义:

Consumer c = (o) -> {
    System.out.println(o);
}; 

上面已说明,函数式编程接口都只有一个抽象方法,因此在采用这种写法时,编译器会将这段函数编译后当作该抽象方法的实现。 

如果接口有多个抽象方法,编译器就不知道这段函数应该是实现哪个方法的了。 

因此,=号后面的函数体我们就可以看成是accept函数的实现。

输入:->前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入是可以有多个的,如两个参数时写法:(a, b);当然也可以没有输入,此时直接就可以是()。

函数体:->后面的部分,即被{}包围的部分;可以是一段代码。

输出:函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。

当函数体中只有一个语句时,可以去掉{}进一步简化:

Consumer c = (o) -> System.out.println(o);

然而这还不是最简的,由于此处只是进行打印,调用了System.out中的println静态方法对输入参数直接进行打印,因此可以简化成以下写法:

Consumer c = System.out::println;

它表示的意思就是针对输入的参数将其调用System.out中的静态方法println进行打印。 

到这一步就可以感受到函数式编程的强大能力。 

通过最后一段代码,我们可以简单的理解函数式编程,Consumer接口直接就可以当成一个函数了,这个函数接收一个输入参数,然后针对这个输入进行处理;当然其本质上仍旧是一个对象,但我们已经省去了诸如老方式中的对象定义过程,直接使用一段代码来给函数式接口对象赋值。 

而且最为关键的是,这个函数式对象因为本质上仍旧是一个对象,因此可以做为其它方法的参数或者返回值,可以与原有的代码实现无缝集成!

下面对Java中的几个预先定义的函数式接口及其经常使用的类进行分析学习。

Java函数式接口

Consumer

Consumer是一个函数式编程接口; 顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出的accept接口方法; 

除accept方法,它还包含有andThen这个方法;

 

其定义如下:

default Consumer< T > andThen(Consumer< ? super T > after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}

可见这个方法就是指定在调用当前Consumer后是否还要调用其它的Consumer; 

使用示例:

public static void consumerTest() {
    Consumer f = System.out::println;
    Consumer f2 = n -> System.out.println(n + "-F2");

    //执行完F后再执行F2的Accept方法
    f.andThen(f2).accept("test");

    System.out.println("------------------");
    
    //连续执行F的Accept方法
    f.andThen(f).andThen(f).andThen(f).accept("test1");
}

输出结果:(是不是感觉很绕?)

test

test-F2

------------------

test1

test1

test1

test1

2.2 Function

Function也是一个函数式编程接口;它代表的含义是“函数”,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出; 

除apply方法外,它还有compose与andThen及indentity三个方法,其使用见下述示例;

/**
 * Function测试
 */
public static void functionTest() {
    Functionf = s -> s++;
    Functiong = s -> s * 2;

    /**
     * 下面表示在执行F时,先执行g,并且执行F时使用g的输出当作输入。
     * 相当于以下代码:
     * Integer a = g.apply(2);
     * System.out.println(f.apply(a));
     */
    System.out.println(f.compose(g).apply(2));

    /**
     * 表示执行F的Apply后使用其返回的值当作输入再执行g的Apply;
     * 相当于以下代码
     * Integer a = f.apply(2);
     * System.out.println(g.apply(a));
     */
    System.out.println(f.andThen(g).apply(2));

    /**
     * identity方法会返回一个不进行任何处理的Function,即输出与输入值相等; 
     */
    System.out.println(Function.identity().apply("a"));
}

输出结果:

4

4

a

2.3 Predicate

Predicate为函数式接口,predicate的中文意思是“断定”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为True或者False。 

它的使用方法示例如下:

/**
 * Predicate测试
 */
private static void predicateTest() {
    Predicatep = o -> o.equals("test");
    Predicateg = o -> o.startsWith("t");

    /**
     * negate: 用于对原来的Predicate做取反处理;
     * 如当调用p.test("test")为True时,调用p.negate().test("test")就会是False;
     */
    Assert.assertFalse(p.negate().test("test"));

    /**
     * and: 针对同一输入值,多个Predicate均返回True时返回True,否则返回False;
     */
    Assert.assertTrue(p.and(g).test("test"));

    /**
     * or: 针对同一输入值,多个Predicate只要有一个返回True则返回True,否则返回False
     */
    Assert.assertTrue(p.or(g).test("ta"));
}

使用函数式代码的好处:

1. 减少了可变量(Immutable Variable)的声明

2. 能够更好的利用并行(Parallelism)

3. 代码更加简洁和可读

陛下...看完奏折,点个赞再走吧!

推荐阅读

技术:奇怪的Java题:为什么128 == 128返回为false,而127 == 127会返回为true?

技术:java堆、栈、堆栈,常量池的区别,史上最全总结

技术:前后端分离--整套解决方案

技术:Java面试必备技能

技术:docker私有仓库搭建,证书认证,鉴权管理

技术:如何成功抢到回家的火车票!

技术:Linux 命令行快捷键

技术:超详细黑苹果安装图文教程送EFI配置合集及系统

技术:恐怖的广告推送。其实,我们每天都在“裸奔”!

技术:如果我不说,你能看出这个主播不是真人吗?

技术:IaaS,PaaS和SaaS,QPS,RT和TPS,PV,UV和IP到底是什么意思?

技术:项目多环境切换——Maven Profile

工具:如何通过技术手段 “干掉” 视频APP里讨厌广告?

工具:通过技术手段 “干掉” 视频APP里讨厌的广告之(腾讯视频)

博主12年java开发经验,现从事智能语音工作的研发,关注微信公众号与博主进行技术交流!更过干货资源等你来拿!

640?wx_fmt=jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值