11111111111
知识共享平台
知识共享平台

讨教大学平台

  • 首页
  • 免费课
  • 精品课
  • 讨教题库
  • 企业服务

    hot

  • 下载APP
  • 证书查询
  • 关于我们
我问
讨教号
搜索
消息
  • 我的文章

    我的关注

    我的问答

    我的秘密

    我的评论

    我的订阅

    我的打赏

    我的钱包

    我的通知

    我的设置

    退出登录

  • ×

    登录

    讨教 | 通行证

    登录
    立即注册
    忘记密码?
    使用微信登录

    提问 ×

    写下你的问题,准确的表述更容易得到答案

    类型话题

    选择支付方式
    您的讨教币 111 付费金额

    Java8新特点学习

    AI前线
    2019-08-08 10:52:49
    6篇 作品
    596 总阅读量

    什么是函数式编程


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


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


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


    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. 代码更加简洁和可读

    本网站内容仅代表作者本人的观点,不代表本网站的观点和看法,与本网站立场无关,如有侵权请联系讨教。
    给作者打赏,鼓励TA抓紧创作
    1人打赏金额339.10
    AI前线
    6篇 作品
    596 总阅读量
    评论
    您可能感兴趣的文章

    广帅老师|从“丰田屋”到“精益塔”的卓越品质之旅

    质量老鸟吕大娘复出记(第4章):改变世界的机器

    现场究竟应该看什么?这篇文章会给你答案!

    防呆防错这样做,提升品质,增加效益!

    【干货】目视化信息在管理上如何传达

    【经典】A3报告培训资料免费领取

    热门话题 更多话题
    精益生产 质量管理 智能制造
    职场效率 项目管理 讨教
    AI 大数据 六西格玛
    ×

    给作者打赏,鼓励TA抓紧创作!

    选择支付方式
    选择打赏金额
    注:打赏的收益归作者,非平台

    微信扫描支付

    打赏金额: 1元

    ×

    给作者打赏,鼓励TA抓紧创作!

    您的讨教币
    填写您打赏讨教币数量
    输入密码

    111

    注:打赏的收益归作者,非平台

    微信扫描支付

    打赏金额: 1元