一、Lambda表达式
1、Lambda表达式语法
三个部分:参数列表,箭头,主体
eg:
(参数列表) -> 主体;
()-> System.out.println("Hello World,Lambda");
2、从匿名类到Lambda的转换
代码:
public static void main(String[] args) {
/**
* 匿名内部类
*/
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
};
runnable.run();
/**
* Lambda表达式的实现
*/
Runnable runnable1 = () -> System.out.println("Hello World,Lambda");
runnable1.run();
}
执行结果
3、Lambda表达式的使用场景
在函数式接口中使用
4、类型检查
Lambda的类型是从使用Lambda的上下文推断出来的
5、类型推断
Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式
6、使用局部变量
局部变量;在主体引用之外声明的变量。注意,局部变量必须显式声明为final,或事实上是final
二、函数式接口
定义:函数式接口就是只定义了一个抽象方法的接口
eg:
/************************* Runnable 接口 ********************** /
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
/************************* Comparator 接口 ********************** /
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
......
}
1、@FunctionalInterface注解
该标注表示该接口会设计成一个函数式接口。如果你用@FunctionalInterface定义了一个接口,而它不是函数式接口的话,编译器将返回一个提示原因的错误。例如,错误消息可能是“Multiple non-overriding abstract methods found in interface Foo”,表明存在多个抽象方法。请注意,@@FunctionalInterface不是必需的,但对于为此涉及的接口而言,使用它是比较好的做法。
2、常见函数式接口
Java内置四大函数式接口
3、专门为基本类型设计的函数式接口,避免装箱拆箱操作
eg:
三、引用
1、方法引用
- 类::静态方法
- 类::实例方法
- 对象::实例方法
第一种类似于静态类的调用,第二种引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。第三种方法引用指的是,你在Lambda中调用一个已经存在的外部对象中的方法
2、构造器引用
格式
类名::new
其中,构造器参数列表要与接口中抽象方法的参数列表一致
3、数组引用
四、流
定义:从支持数据处理操作的源生成的元素序列
1、流与集合
集合与流之间的差异就在于什么时候进行计算。集合是急切计算的,而流则是按需计算的,具有延时性。例如谷歌搜索,若是集合,则会将匹配的所有信息一次性查出放入,而流可能就是一页一页的查询,即你看完当前页,点击下一页时才会触发下一页数据的查询。
2、和迭代器类似,流只能遍历一次
3、streams库的内部迭代可以自动选择一种适合你硬件的数据表示和并行实现,流利用内部迭代:迭代通过filter、map、sorted等操作被抽象掉
4、流操作
- 中间操作
- 终端操作
注意:除非流水线上触发一个终端操作,否则中间操作不会执行任何处理
五、流的使用
1、映射map:对流中的每一个元素执行该操作,其本质是经一个对象映射成另一个对象
2、流的扁平化:map和flatmap
其中stream中封装的对象对应map映射后的类型。flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
3、查找和匹配:allMatch、anyMatch、noneMatch、findFirst和findAny
注意:findFirst和findAny,findAny在并行上较findFirst限制较少。
4、规约:reduce
//接受初始值
int product = numbers.stream().reduce(1, (a, b) -> a * b);
//不接受初始值
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
5、状态
- 有状态:需要知道之前状态,如reduce、sum、max
- 无状态:不需要知道内部迭代状态,如map或filter
六、Collectors类
1、oining工厂方法有一个重载版本可以接受元素之间的分界符
Stringstr=list.stream().map(Employee::getName).collect(Collectors.joining(","));
2、ruduce与collect的区别:educe方法旨在把两个值结合起来生成一个新值,它是一个不可变的规约。而collec方法的设计就是要改变容器,从而累积要输出的结果
3、groupingby的可以接受第二个参数,与其他收集器联合起来使用,例如
Map<Dish.Type, Integer> totalCaloriesByType =menu.stream().collect(groupingBy(Dish::getType,summingInt(Dish::getCalories)));
七、新时间日期API
1、LocalDate、LocalTime、LocalDateTime
这些类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间
2、Duration与Period
Duration:用于计算两个"时间"间隔
Period:用于计算两个"日期"间隔
3、时间调整期
4、总结
- 新版的日期和时间API中,日期时间对象是不可变的
- 操纵日期和时间时,操作的结果总是返回一个新的实例,老的日期时间对象不会发生变化。
八、总结
- Lambda表达式其实就是对匿名内部类的演变,其方法主体其实就是匿名内部内方法的主体。
- stream流采用的内部迭代通常比for-each的显示迭代效率更高
- stream流在执行中间操作时,只是将一系列操作中间操作组合,当有终端操作时才会全部触发
九、日常问题
1、list转map的坑
参考:Java8新特性Stream之list转map及问题解决
结论:用Collectors的toMap方法转换List,一般会遇到两个问题。一个是转换map,key重复问题;另一个是空指针异常,即转为map的value是null
(1)转换map,key重复问题解决方案
- 重复时用后面的value 覆盖前面的value
- 重复时将前面的value 和后面的value拼接起来
- 重复时将重复key的数据组成集合
(2)空指针异常:在转换流中加上判空,即便value为空,依旧输出