解构Lambda表达式

2023-03-06 21:46:37 浏览数 (2)

解构Lambda表达式

1.1 行为参数化传递给方法

java8将方法和函数,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

代码语言:javascript复制
@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表达式就可以赋值给不通的函数式接口。

代码语言:javascript复制
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,对于不同签名的函数式接口,只要满足签名,就可以做到相对应的适配。

代码语言:javascript复制
//()->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

代码语言:javascript复制
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);
}

0 人点赞