java8新特性function和lambda深度解析

Wesley13
• 阅读 739

继续java8新亮点的源码之路,functional interface是一个跳不过的坎,它与lambda的结合使用非常普遍。java.util.function包对于每一个java工程师来说是必备技能,也是最基础的能力,一定要掌握。

head-icon

函数编程的最直接的表现在于将函数作为数据自由传递,结合泛型推导能力使代码表达能力获得飞一般的提升。同时Lambda表达式让你能够将函数作为方法参数或者将代码作为数据对待,让你发现“行级代码”优美。

java8引入新的注解,@FunctionalInterface

函数式注解@FunctionalInterface添加在一个接口上,主要是编译器检查提示作用。

  1. 注解的作用是检测自定义functional接口是否符合要求,编译器会有错误提示;
  2. 一个接口符合functional的要求,不加这个注解也可以正常使用,建议都加上
  3. 有且只能有一个抽象方法但可以有多个非抽象方法,简单说就是接口里面default和static的方法是可以有多个的,其他的方法只能有一个。

lambda表达式写法及注意点

格式:
( parameters ) -> { statements; }

  1. 不需要声明参数类型,编译器可以识别参数值;
  2. 单个参数和语句下,圆括弧和大括弧可以省略;
  3. 表达式是一个闭包,定义了行内执行的方法类型接口;
  4. 只能引用标记了final的外层局部变量,不能在表达式内部修改定义在域外的局部变量,否则会编译错误;
  5. 表达式当中不允许声明一个与局部变量同名的参数或者局部变量;

写法示例:

@FunctionalInterface
public interface IPerson {

    String say(String input);
    //void stand(); 只能有一个抽象方法,不然编译无法默认识别调用

    static void run(String xx){
        PrintUtil.printTest("IPerson run : " + xx);
    }

    static void walk(){
        PrintUtil.printTest("IPerson walk");
    }

    default void eat(int a, int b){
        PrintUtil.printTest("IPerson eat : " + a + " - " + b);
    }
}

//当你这种写法是编译器会提示你用lambda
IPerson person = new IPerson() {
    @Override
    public String say(String input) {
        return "My said is " + input;
    }
};
PrintUtil.printTest(person.say("i love china."));

//lambda写法
IPerson person2 = a -> "My said is " +a;
PrintUtil.printTest(person2.say("i love china."));
//结果是一样的,My said is i love china.

function包中重要接口源码分析

java8function

Consumer,接收一个输入参数T类型并不没有返回值;andThen看源码可以知道是添加一个其后执行的Consumer对象。

这个接口很简单不需要什么解释,看源码一眼OK。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

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

Function,接收一个T类型参数,返回一个R类型的结果。需要注意的是compose\andThen的传入参数和范围参数规则不同,这里的参数类型稍有不慎就会出错,复杂的链路里面排查bug是非常麻烦的事。

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);
    //生成了function的参数类型同before一样
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    //新生成的function的返回值类型要after一样
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    //
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

测试用例:

Function<Integer,String> function = a -> "== " + a;
PrintUtil.printTest(function.apply(101));
Function<String,Boolean> function1 = c -> c.length()>2;
PrintUtil.printTest(function1.apply("1a"));

PrintUtil.printTest(function.andThen(function1).apply(111));
//andThen类似consumer,是前一个function执行后结果作为参数传新生成的function执行,结构:true

Function<Integer,Integer> function2 = c -> c*c;
//compose和andThen正好逻辑相反,传入的参数function先执行后范围结果作为参数传给新生成的function执行
PrintUtil.printTest(function.compose(function2).apply(2));
//先执行function2,返回结果作为参数再执行function,结果:== 4
PrintUtil.printTest(function.compose(function2).andThen(function1).apply(2));
//先执行function2,其次执行funciton,最后执行function1,结果:true
PrintUtil.printTest(function2.compose(function2).apply(2));
//先执行第二个function2,返回结果作为参数再执行第一个function2,结果:16
//递归的实现又多了种办法

Function<String,String> function3 = Function.identity();//static方法
PrintUtil.printTest(function3.apply("hello"));
//identity定义了一个只返回输入参数的function,结果:hello

confuse

Predicate,是一个条件判断接口,接收一个T参数范围boolean值,默认抽象方法test(t);and\or\negate分别对应逻辑与、或、非操作,isEqual。

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    //逻辑与
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    //获取该对象否定的Predicate,相当于逆转boolean
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    //逻辑或
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    //生成一个判断对象是否相等的Predicate
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

测试用例:

Predicate<ICar> carHas4Wheel = a -> a.getWheelCount() > 3;
ICar weilai = new WeiLaiCar(3);

PrintUtil.printTest(carHas4Wheel.test(weilai));
//false
PrintUtil.printTest(carHas4Wheel.negate().test(weilai));
//true
PrintUtil.printTest(carHas4Wheel.and(b -> b.getWheelCount()>2).test(weilai));
//false
PrintUtil.printTest(carHas4Wheel.and(b -> b.getWheelCount()>2).test(new WeiLaiCar(4)));
//true
PrintUtil.printTest(carHas4Wheel.or(b -> b.getWheelCount()>2).test(new WeiLaiCar(3)));
//true

PrintUtil.printTest(Predicate.isEqual(weilai).test(weilai));
//true
PrintUtil.printTest(Predicate.isEqual(weilai).test(new WeiLaiCar(3)));
//false

Supplier,是一个只有返回没有参数的接口,只有一个方法get。

@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     * @return a result
     */
    T get();
}

测试用例:

Supplier<Integer> supplier = () -> 1 ;
PrintUtil.printTest(supplier.get());
//1
Supplier<ICar> carSupplier = () -> new WeiLaiCar(5) ;
PrintUtil.printTest(carSupplier.get() + " : " + carSupplier.get().getWheelCount());
//com.ts.util.optional.WeiLaiCar@32a1bec0 : 5

holdOn

Consumer<T> Predicate<T> Supplier<T> Function<T, R> BiFunction<T, U, V>,这里的T U R V并没有特别的定义只是一种约定,就像驼峰命名和泛型中K V E一样。

分享连接

除了介绍的几种函数式接口外,java8在这几个基础上封装了很多的延伸接口,如BiConsumer\ DoubleConsumer\ IntConsumer\ LongConsumer\ ObjIntConsumer等。平时写代码时注意积累,闲的时候多去看看API,慢慢的就掌握了。

Lambda和Functional的结合很大一点就是代码简洁了,看着非常的赏心悦目。JAVA一个纯面向对象的语言,在行级代码上一直是非常的简洁易于调试,而这两个新特性的出现让行级代码的复杂度急剧提升。

相关用例代码已托管Github:play-java-sample

推荐关注

作者:Owen Jia。
推荐关注他的博客:Owen Blog

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Lambda表达式中Collections的接口有哪些变化?
我们先从最熟悉的\Java集合框架(JavaCollectionsFramework,JCF)\开始说起。为引入Lambda表达式,Java8新增了java.util.function包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分接口,以便与Lambda表达式对接。首先回顾一下Java集合框架的接口继承
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
3年前
Java8特性
Java8又称jdk1.8。主要新特性:Lambda表达式 −Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。方法引用 −方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
Wesley13 Wesley13
3年前
Java8函数式编程(A)
将行为作为数据传递函数编程的最直接的表现,莫过于将函数作为数据自由传递,结合泛型推导能力,使代码表达能力获得飞一般的提升。Java8怎么支持函数式编程?主要有三个核心概念:函数接口(Function)流(Stream)聚合器(Collector)函数接口关于函数接口,两件事:函数接口是行为
Wesley13 Wesley13
3年前
Java8—一万字的Lambda表达式的详细介绍与应用案例
  基于Java8详细介绍了lambda表达式的语法与使用,以及方法引用、函数式接口、lambda复合等Java8的新特性!文章目录1Lambda的概述2函数式接口2.1Consumer消费型接口2.2Supplier供给型接口2.3Function<T,R函数型接口
Wesley13 Wesley13
3年前
Java8新特性学习
1简述公司自年初终于开始使用java8作为项目的标准jdk,在开发过程中,逐渐认识到java8的很多新特性,确实很方便.其中内容的核心,在于函数式编程,即将函数本身作为对象参数去处理.其中涉及到三个关键新特性:1.lambda表达式(及函数式接口)2.stream3.方法引用这三个新特性的使用是相辅相
Wesley13 Wesley13
3年前
Java 8 stream 实战
概述平时工作用python的机会比较多,习惯了python函数式编程的简洁和优雅。切换到java后,对于数据处理的『冗长代码』还是有点不习惯的。有幸的是,Java8版本后,引入了Lambda表达式和流的新特性,当流和Lambda表达式结合起来一起使用时,因为流申明式处理数据集合的特点,可以让代码变得简洁易读。幸福感爆棚,有没有!本文主要列举一些