解构Lambda表达式
1.1 行为参数化传递给方法
java8将方法和函数,Lambda
函数提升为一等公民,可以作为值进行传递。
传统的行为参数化
- 通过对象值作为参数传递的,在对象中定义的方法封装了行为。通过合理的接口,抽象出顶层的父接口,设计加多个子类实现达到多种不同的行为通过一个参数进行传递。缺点是对于每一次的行为处理都需要实例化一个子类对象,可能这个对象只用了一次,太浪费了。
- 匿名类,可以同时声明并实例化一个类,随用随建。缺点是还需要写一个创建对象并实现方法的大块头,用多了很臃肿
- 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表达式就可以赋值给不通的函数式接口。
代码语言: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或者这个局部变量不会再赋值改变。
造成已上情况的原因:
- 实例变量保存在java堆中,局部变量保存在java的方法栈中,多线程情况下可能这个局部变量已经被回收了,而此时还有访问的情况可能会发生。
- Lambda对值封闭,对变量开放。
1.2.5 方法引用
一种Lambda表达式的语法糖,针对的是Lambda中只涉及一种方法的情况,可以复用已存在的方法并传递给函数式接口。有如下的表现,可以使用语法糖对原有的Lambda进行转换:
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"));(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"));(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);
}