lambda表达式可以这样定义:一段带有输入参数的可执行语句块。
lambda表达式的一般语法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型, 参数类型省略。 这样lambda表达式就变成了:
(param1,param2, ..., paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
当lambda表达式的参数个数只有一个,可以省略小括号。lambda表达式简写为:
param1 -> {
statment1;
statment2;
//.............
return statmentM;
}
当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。lambda表达式简化为:
param1 -> statment
expression lambda和statement lambda的区别是,expression lambda不需要 写return关键字,Java runtime会将表达式的结果作为返回值返回,而statement lambda是 写在{}中的表达式,需要使用return关键字,比如:
// expression lambda
Comparator<String> comp1 =
(first, second) -> Integer.compare(first.length(), second.length());
// statement lambda
Comparator<String> comp2 = (first, second) ->
{ return Integer.compare(first.length(), second.length());};
使用Method Reference:
List<String> lowercaseNames = names.stream().map(String::toLowerCase).collect(Collectors.toList());
如果将lambda表达式的参数作为参数传递给一个方法,他们的执行效果是相同的,则该lambda表达式 可以使用Method Reference表达,以下两种方式是等价的:
(x) -> System.out.println(x)
//等价于:
System.out::println
其中System.out::println被称为Method Reference。 Method Reference主要有三种形式: object::instanceMethod Class::staticMethod Class::instanceMethod 对于前两种方式,对应的lambda表达式的参数和method的参数是一致的,比如:
System.out::println
(x) -> System.out.println(x)
Math::pow
(x, y) -> Math.pow(x, y)
对于第三种方式,对应的lambda表达式的语句体中,第一个参数作为对象,调用method,将其它参数 作为method的参数,比如:
String::compareToIgnoreCase
(s1, s2) -> s1.compareToIgnoreCase(s2)
使用Constructor Reference: Constructor Reference与Method Reference类似,只不过是特殊的method:new,具体调用的是哪个构造函数,由上下文环境决定,比如:
List<String> labels = ...;
Stream<Button> stream = labels.stream().map(Button::new);
Button::new等价于(x) -> Button(x),所以调用的构造函数是:Button(x); 除了创建单个对象,也可以创建对象数组,如下面两种方式等价:
int[]::new
(x) -> new int[x]
lambd表达式会捕获当前作用域下可用的变量,比如:
public void repeatMessage(String text, int count) {
Runnable r = () -> {
for (int i = 0; i < count; i ++) {
System.out.println(text);
Thread.yield();
}
};
new Thread(r).start();
}
但是这些变量必须是不可变的,为什么呢?看下面这个例子:
int matches = 0;
for (Path p : files)
new Thread(() -> { if (p has some property) matches++; }).start();
// Illegal to mutate matches
因为可变的变量在lambda表达式中不是线程安全的,这和内部类的要求是一致的,内部类中只能引用 外部定义的final变量;
lambda表达式的作用域与嵌套代码块的作用域是一样的,所以在lambd表达式中的参数名或变量名不 能与局部变量冲突.
如果在lambda表达式中引用this变量,则引用的是创建该lambda表达式的方法的this变量,如:
public class Application() {
public void doWork() {
Runnable runner = () -> {
...;
System.out.println(this.toString());
...
};
}
}
所以这里的this.toString()调用的是Application对象的toString(),而不是Runnable对象的。
函数式接口(Functional Interface): 所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。这种类型的接口也称为SAM接口,即Single Abstract Method interfaces.
Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。加不加@FunctionalInterface对于接口是不是函数式接口没有影响,该注解只是提醒编译器去检查该接口是否仅包含一个抽象方法。
@FunctionalInterface
interface Print<T> {
public void print(T x);
}
public class Lambda {
public static void PrintString(String s, Print<String> print) {
print.print(s);
}
public static void main(String[] args) {
PrintString("test", (x) -> System.out.println(x));
}
}
编译后等价于:
@FunctionalInterface
interface Print<T> {
public void print(T x);
}
public class Lambda {
public static void PrintString(String s, Print<String> print) {
print.print(s);
}
private static void lambda$0(String x) {
System.out.println(x);
}
final class $Lambda$1 implements Print{
@Override
public void print(Object x) {
lambda$0((String)x);
}
}
public static void main(String[] args) {
PrintString("test", new Lambda().new $Lambda$1());
}
}
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:
- BiConsumer<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果
- BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果
- BinaryOperator 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
- BiPredicate<T,U> 代表了一个两个参数的boolean值方法
- BooleanSupplier 代表了boolean值结果的提供方
- Consumer 代表了接受一个输入参数并且无返回的操作
- DoubleBinaryOperator 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
- DoubleConsumer 代表一个接受double值参数的操作,并且不返回结果。
- DoubleFunction 代表接受一个double值参数的方法,并且返回结果
- DoublePredicate 代表一个拥有double值参数的boolean值方法
- DoubleSupplier 代表一个double值结构的提供方
- DoubleToIntFunction 接受一个double类型输入,返回一个int类型结果。
- DoubleToLongFunction 接受一个double类型输入,返回一个long类型结果
- DoubleUnaryOperator 接受一个参数同为类型double,返回值类型也为double 。
- Function<T,R> 接受一个输入参数,返回一个结果。
- IntBinaryOperator 接受两个参数同为类型int,返回值类型也为int 。
- IntConsumer 接受一个int类型的输入参数,无返回值 。
- IntFunction 接受一个int类型输入参数,返回一个结果 。
- IntPredicate :接受一个int输入参数,返回一个布尔值的结果。
- IntSupplier 无参数,返回一个int类型结果。
- IntToDoubleFunction 接受一个int类型输入,返回一个double类型结果 。
- IntToLongFunction 接受一个int类型输入,返回一个long类型结果。
- IntUnaryOperator 接受一个参数同为类型int,返回值类型也为int 。
- LongBinaryOperator 接受两个参数同为类型long,返回值类型也为long。
- LongConsumer 接受一个long类型的输入参数,无返回值。
- LongFunction 接受一个long类型输入参数,返回一个结果。
- LongPredicate R接受一个long输入参数,返回一个布尔值类型结果。
- LongSupplier 无参数,返回一个结果long类型的值。
- LongToDoubleFunction 接受一个long类型输入,返回一个double类型结果。
- LongToIntFunction 接受一个long类型输入,返回一个int类型结果。
- LongUnaryOperator 接受一个参数同为类型long,返回值类型也为long。
- ObjDoubleConsumer 接受一个object类型和一个double类型的输入参数,无返回值。
- ObjIntConsumer 接受一个object类型和一个int类型的输入参数,无返回值。
- ObjLongConsumer 接受一个object类型和一个long类型的输入参数,无返回值。
- Predicate 接受一个输入参数,返回一个布尔值结果。
- Supplier 无参数,返回一个结果。
- ToDoubleBiFunction<T,U> 接受两个输入参数,返回一个double类型结果
- ToDoubleFunction 接受一个输入参数,返回一个double类型结果
- ToIntBiFunction<T,U> 接受两个输入参数,返回一个int类型结果。
- ToIntFunction 接受一个输入参数,返回一个int类型结果。
- ToLongBiFunction<T,U> 接受两个输入参数,返回一个long类型结果。
- ToLongFunction 接受一个输入参数,返回一个long类型结果。
- UnaryOperator 接受一个参数为类型T,返回值类型也为T。
List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
System.out.println("sum is:"+nums.stream().filter(num -> num != null).
distinct().mapToInt(num -> num * 2).
peek(System.out::println).skip(2).limit(4).sum());
疑问:在对于一个Stream进行多次转换操作,每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是一个for循环里把所有操作都做掉的N(转换的次数)倍啊。其实不是这样的,转换操作都是lazy的,多个转换操作只会在汇聚操作(见下节)的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。