美文网首页
弄懂Java8的lambda表达式,读源码更省力

弄懂Java8的lambda表达式,读源码更省力

作者: 逆熵架构 | 来源:发表于2023-02-28 15:02 被阅读0次

    1.Lambda表达式

    1.1 行为参数化传递给方法

    将方法和函数,Lambda函数提升为一等公民,可以作为值进行传递。

    传统的行为参数化

    1. 通过对象值作为参数传递的,在对象中定义的方法封装了行为。通过合理的接口,抽象出顶层的父接口,设计加多个子类实现达到多种不同的行为通过一个参数进行传递。缺点是对于每一次的行为处理都需要实例化一个子类对象,可能这个对象只用了一次,太浪费了。
    2. 匿名类,可以同时声明并实例化一个类,随用随建。缺点是还需要写一个创建对象并实现方法的大块头,用多了很臃肿
    3. Lambda表达式,直接将行为方法作为参数传递给方法。a -> system.out.println(a.getName());

    1.2 Lambda——匿名函数

    把方法作为值传递虽然很有用,但是有时有些方法我们不想具体定义,这时候Lambda函数就登场了。Lambda表达式的基本语法是(parameters)->expression或者(parameters)->{statements;}

    Lambda表达式的组成部分:参数列表+箭+Lambda主体(如果是表达式,可以省略{},如果是语句必须加入{})

    Lambda表达式可以被赋值给一个变量,也可以传递给一个接受函数式的接口作为参数的方法。

    1.2.1 函数式接口

    定义:只定义一个抽象方法的接口,其中默认方法不算在内。

    @FunctionalInterface注解不是必需的,它只是告诉使用者这是一个函数式接口,并且在一个标注了该注解的接口中如果存在除了默认方法或者继承Object方法之外的抽象方法的个数多于1个的时候,编译器会提示报错Multiple non-overriding abstract methods found in interface Lambdasinaction.chap3.Lambdas.checkedConsumer

    @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);
    }
    

    关于异常,任何函数式接口都不允许抛出检查异常(checked Exception),对于异常的处理,有两种做法,一种是将Lambda表达式包在try...catch中重新抛出运行时异常,还有种做法是自己重新定义一个抛出使用检查异常的函数式接口。

    1.2.2 函数描述符

    定义:函数式接口的抽象方法的签名我们称之为函数描述符,而抽象方法的签名等同于Lambda表达式的签名。比如用以下表示法()->void 来描述 Runnable接口 ,对Predicate<T>的函数描述符就是T->boolean

    1.2.3 Lambda的类型检查

    Lambda表达式生成的函数式接口实例如何对应函数式接口呢?

    首先,Lambda的类型是从上下文中推断出来的。我们声明了函数式接口作为参数,从函数式接口的函数描述符可以知道入参,返回值还有是否抛出异常的定义。只要以上几个定义和lamdba表达式一致,那么这个lamdba表达式就可以赋值给不同的函数接口。比如以下同一个Lambda表达式就可以赋值给不通的函数式接口。

    Comparator<Integer> comparator = 
        (Integer a1, Integer a2) -> a1.compareTo(a2);
    ToIntBiFunction<Integer,Integer> intBF=
        (Integer a1, Integer a2) -> a1.compareTo(a2);
    BiFunction<Integer,Integer,Integer> bf=
        (Integer a1, Integer a2) -> a1.compareTo(a2);
    

    对于以上中的参数类型,java编译器也可以通过目标类型来推断参数的真实类型。

    1.2.4 局部变量和外层变量

    当Lambda表达式中使用外部的实例变量和静态变量没有什么限制,但是对于外部的局部变量需要申明为final或者这个局部变量不会再赋值改变。

    造成已上情况的原因:

    1. 实例变量保存在java堆中,局部变量保存在java的方法栈中,多线程情况下可能这个局部变量已经被回收了,而此时还有访问的情况可能会发生。
    2. Lambda对值封闭,对变量开放。

    1.2.5 方法引用

    一种Lambda表达式的语法糖,针对的是Lambda中只涉及一种方法的情况,可以复用已存在的方法并传递给函数式接口。有如下的表现,可以使用语法糖对原有的Lambda进行转换:

    1. args->ClassName.staticMethod(agrs) 转换成方法引用:ClassName::staticMethod,通俗地讲,就是Lambda调用了一个类的静态方法,并且满足了函数接口的签名。

      Function<String,Integer> func=s->Integer.parseInt(s);
      System.out.println(func.apply("122222"));
      //二者相等
      Function<String,Integer> func1=Integer::parseInt;
      System.out.println(func1.apply("1222223"));
      
    2. (inst0,agrs)->inst0.instanceMethod(agrs) ,inst0是ClassName类型,

      转换成方法引用:ClassName::instanceMethod,通俗地讲,Lambda主体中引用的某个对象的方法,该对象就是Lambda中的参数。

      Function<String,Integer> func3=s->s.length();
      System.out.println(func3.apply("12345"));
      //二者相等
      Function<String,Integer> func4=String::length;
      System.out.println(func4.apply("1234"));
      
    3. (args)->extra.instanceMethod(args),转换成方法引用:extra::instanceMethod,通俗地讲,就是Lambda主体引用的一个外部实例的方法,并且满足函数式接口的签名。

      Extra extra=new Extra();
      Function<String,String> func5=s->extra.getName(s);
      Function<String,String> func6=extra::getName;
      System.out.println(func5.apply("12345"));
      System.out.println(func6.apply("1234"));
      

    1.2.6 构造函数引用

    对于类的构造函数,可以使用类似静态方法的方式ClassName::new,对于不同签名的函数式接口,只要满足签名,就可以做到相对应的适配。

    //()->T
    Supplier<Extra> supplier=Extra::new;
    final Extra extra1 = supplier.get();
    System.out.println(extra1);
    //T->R
    Function<String,Extra> function=Extra::new;
    final Extra extra2 = function.apply("test");
    System.out.println(extra2);
    //T,U->R
    BiFunction<String,String,Extra> biFunction=Extra::new;
    final Extra extra3 = biFunction.apply("test", "new");
    System.out.println(extra3);
    //T,U,M->R
    TreeFunction<String,String,Integer,Extra> treeFunction=Extra::new;
    final Extra extra4 = treeFunction.newObj("test3", "new3", 2);
    System.out.println(extra4);
    

    1.5 默认方法

    为了达到 已发布的接口改变(新增,修改方法)不影响已有的实现类接口的目的,接口声明中新增default关键字,用于新增接口新的方法,比如Collection中新增stream/parallelStream

    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    
    /**
         * Returns a possibly parallel {@code Stream} with this collection as its
         * source.  It is allowable for this method to return a sequential stream.
         *
         * <p>This method should be overridden when the {@link #spliterator()}
         * method cannot return a spliterator that is {@code IMMUTABLE},
         * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
         * for details.)
         *
         * @implSpec
         * The default implementation creates a parallel {@code Stream} from the
         * collection's {@code Spliterator}.
         *
         * @return a possibly parallel {@code Stream} over the elements in this
         * collection
         * @since 1.8
         */
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
    

    相关文章

      网友评论

          本文标题:弄懂Java8的lambda表达式,读源码更省力

          本文链接:https://www.haomeiwen.com/subject/qdelldtx.html