Lambda表达式介绍和Stream API解析
1. lambda表达式
百度百科:
Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包。
Lambda 表达式是Java8推出的重要的特性之一(java8新特性介绍),允许把函数作为一个方法的参数传入,是面向函数式编程的思想,一定程度上可以使代码更简洁,它的本质就是一个语法糖。
传统方式创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("传统方式创建线程");
}
}).start();
使用lambda方式创建线程
new Thread(() -> System.out.println("lambda方式创建线程")).start();
lanmbda看起来非常简洁易懂,lambda还能够推断出类型是Runable,很神奇吧。
lambda表达式语法分为三部分:1)方法输入 2)箭头函数 3)方法体
(parameters) -> expression
或
(parameters) ->{ statements; }
2. 函数式接口讲解
通过代码简单认识lambda,我们看下java8是让Runable支持lambda的。
查看Runable的源码,看到接口上定义了一个@FunctionalInterface注解
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
查看@FunctionalInterface注解,最主要的是注释,下面代码片段已经把英文的意思进行了总结。
package java.lang;
import java.lang.annotation.*;
/**
1)该注解只能标记在"有且仅有一个抽象方法"的接口上。
2)JDK8接口中的静态方法和默认方法,都不算是抽象方法。
3)接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
4)该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
5)1.8及以后版本开始支持
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
已经了解接口如何支持lambda表达式,现在自己定义一个函数式接口尝试下。
public class LambdaDemo {
/**定义函数式接口*/
public interface ImLambdaInterface {
void print();
}
/**定义执行方法*/
void testLambda(ImLambdaInterface demo) {
demo.print();
}
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
demo.testLambda(() -> System.out.println("你好,我在测试函数式接口"));
}
}
3. jdk自带函数式接口介绍
3.1 Function
说明:Function<T, R>支持传入一个参数T,返回一个参数R
示例:
//使用场景:按照属性进行list排序
list.sort(Comparator.comparingInt(a -> a.getAge()));
//**框架源码使用欣赏:Comparator的比较器就是传入Function进行处理
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
return (Comparator<T> & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
3.2 BiConsumer
说明:BiConsumer<T,U>支持传入两个参数T和U,无返回值
示例:
//使用场景:经常使用的map遍历其实就是BiConsumer
map.forEach((k, v) -> {
System.out.println("我是key" + k + ",我是Value" + v);
});
//**框架源码使用欣赏:HashMap源码直接取出key和value就是使用了BiConsumer
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
//源代码略.......
action.accept(k, v);
}
}
3.3 Supplier
说明:Supplier,支持返回一个泛型对象。直接写简单示例代码可能比较空洞,直接看ThreadLocal源码更容易理解。
示例:
//使用场景:ThreadLocal可以直接使用Supplier
private static final ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "初始值");
//**框架源码使用欣赏:ThreadLocal源码
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
3.4 Predicate
说明:传入一个函数T,返回是否符合要求。Predicate在Stream有广泛的使用场景。
示例:
//使用场景:ArrayList过滤实体属性,可以使用自带的removeIf,这个就是断言
List<Person> list = Stream.of(Person.builder().name("Kobe").age(28).build(),
Person.builder().name("Jordon").age(36).build()).collect(Collectors.toList());
list.removeIf((t) -> t.getAge() > 34);
//**框架源码使用欣赏:ArrayList.removeIf()源码
default boolean removeIf(Predicate<? super E> filter) {
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
4.Stream介绍
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream中的操作可以分为两大类:中间操作与结束操作,中间操作只是对操作进行了记录,只有结束操作才会触发实际的计算(即惰性求值),这也是Stream在迭代大集合时高效的原因之一。中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。结束操作又可以分为短路与非短路操作,这个应该很好理解,前者是指遇到某些符合条件的元素就可以得到最终结果;而后者是指必须处理所有元素才能得到最终结果。
======未完成,待续