什么是Lambda表达式
在编程语言中,lambda指的是一种运算符,用来表示:
closures(闭包)
anonymous functions(匿名函数)
旧的语法通过传递数据来完成,而Lambda表达式更注重于行为。在java中,Lambda表达式是个对象,它们必须依附于一类特别的对象类型 —— 函数式接口(functional interface)
为什么需要Lambda表达式
- 在java中,我们无法将函数作为参数传递的方法,也无法声明返回一个函数的方法。
- javascript中,函数参数是一个函数,返回值是另一个函数的情况是非常常见的。
Lamdba表达式的作用
-
传递行为,而不仅仅是值
- 提升抽象层次
- API重用性更好
- 更加灵活
Lambda表达式的基本结构
(type1 arg1, type2 arg2, ..., typeN argN) -> { body }
-
Lambda示例
- (int a, int b) -> {return a + b;}
- () -> System.out.println("Hello World");
- (String s) -> {System.out.println(s);}
- () -> 42
- () -> {return 4};
- 一个Lambda表达式可以有零个或多个参数
- 参数类型可以明确声明,也可以让编译器通过上下文推断。例如:(int a) 与 (a) 效果相同
- 所有参数需要包含在圆括号内,参数之间用逗号相隔。例如:(a, b)或(int a, int b)或 (String a, int b, double c)
- 圆空括号表示参数集合为空。例如() -> 42
- 当只有一个参数,且其类型可以推断时,圆括号()可以省略。例如: a -> return a*a
- Lambda表达式的主题可包含零条或多条语句
- 如果Lambda表达式的主题只有一条语句,花括号{}可以省略。匿名函数的返回类型与该主体表达式一致。
- 如果Lambda表达式的主题包含一条以上的语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空。
函数式接口
通过Lambda表达式遍历集合
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(new Consumer(){
@Override
public void accept(Integer integer){
System.out.println(integer);
}
});
Consumer接口
@FunctionalInterface
public interface Consumer {
void accept(T t);
default Consumer andThen(Consumer super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
关于函数式接口 注解
@FunctionalInterface
:凡是接口上带上这个注解,即为函数式接口。一个函数式接口有且只有一个精确的抽象方法。函数式接口的实例可以通过Lamdba表达式的方法引用
或构造方法引用
来去创建。
如果使用了@FunctionalInterface
注解,但却不满足以下两个条件,编译器会返回编译异常:
- 这个类型必须是
Interface接口类型
- 这个注解类型必须满足函数式接口的要求:一个函数式接口有且只有一个精确的抽象方法
如果这个抽象方法复写了或者重写了Object
类里的public方法
,那么这个接口也不属于函数式接口。因为java的任意实现,一定都会有一个来自java.lang.Object的实现。
@FunctionalInterface
public interface MyInterface{
// 抽象方法
void test();
// Object的抽象方法
String toString();
}
toString()
是Object的抽象方法,所以编译器认为MyInterface依然是一个函数式接口。
public class UsingFunctionInterface{
public void myUsingInterface(MyInterface interface){
System.out.println("start");
interface.test();
System.out.println("end");
}
public static void main(String[] args){
UsingFunctionInterface u = new UsingFunctionInterface();
// 写法1
u.myUsingInterface(() -> {
System.out.println("test print");
});
//写法2
MyInterface myInterface = () -> {
System.out.println("test print");
};
u.myUsingInterface(myInterface);
}
}
Lamdba表达式弱化了抽象方法名的意义,将方法实现(即->
右边的部分,直接作为函数式接口的实现)。
关于函数式接口的特点
- 如果一个接口只有一个抽象方法,那么这个接口就是函数式接口
- 如果我们在某个接口上声明了
@FunctionalInterface
,那么编译器就会按照函数式接口的定义来要求该接口 - 如果某个接口只有一个抽象方法,但我们并没有给该接口声明
@FunctionalInterface
,那么编译器依旧会将该接口看作是函数式接口。 - 函数式接口的实例,可以通过
Lamdba表达式
、方法的实例
、构造方法的实例
来创建。
上述的总结:@FunctionalInterface
只是帮助编译器检查函数式接口是否合法。
如果一个接口需要用作函数式接口,那么最好加上
@FunctionalInterface
。
foreach方法
list.foreach()
foreach的实现
default void forEach(Consumer super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
foreach最特殊的就是用了default关键字
。jdk1.8后,接口里也可以有方法实现。一旦在接口中使用了default关键字做方法的实现,称之为默认方法(default Method)
。
默认方法的出现一方面保证了jdk8的新特性的加入,另一方面保证了jdk8可以向上兼容。
Consumer
Consumer
是一个操作,这个操作接受一个单个的输入元素,并且不返回结果。与其他函数式接口不同的是,它可能会修改接收到的操作。
Consumer的语义是消费者
,所以顾名思义,这个类中的抽象方法void accept(T t)
的语义为:接受一个类,并将其消费掉,并不关心结果
迭代
外部迭代
:指的是利用外部的迭代器,对一个集合进行迭代。内部迭代
:通过集合本身,利用Lambda表达式进行迭代。
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 外部迭代
for(Integer i : numbers){
System.out.println(i);
}
// 内部迭代 : 函数式接口
numbers.forEach(new Consumer{
public void accept(){
System.out.println(i);
}
});
// 内部迭代: 函数式接口 + Lambda表达式
numbers.forEach(i -> System.out.println(i) )
// 内部迭代: 方法引用
numbers.forEach(System.out::println)
使用stream做迭代
stream的实现
default Stream stream() {
return StreamSupport.stream(spliterator(), false);
}
stream是在Collection接口的默认方法,返回一个串行流。
当spliterator(分割迭代器)
方法不能返回一个分割迭代器时(不可变的、延迟的、并发的分割迭代器),stream方法需要被重写。
stream中的map()
,map本身的含义是映射,将一个给定的元素映射为另一个元素并返回一个新的Stream。
List list = Arrays.asList("a", "b", "c");
List list2 = new ArrayList<>();
// Lamdba表达式实现
list.stream().map(i -> i.toUpperCase()).forEach(i -> list2.add(i));
// 方法引用实现
list.stream().map(String::toUpperCase).forEach(list2::add);