视频地址:https://www.bilibili.com/video/BV1ut411g7E9
笔记 md 文档:https://download.csdn.net/download/qq_43290318/13192803
文档效果图:
Lambda表达式
概念
Lambda是一个匿名函数,Lambda表达式可以理解为一段可以传递的代码(将代码像数据一样进行传递)
引入
/**
* 匿名内部类
*/
@Test
public void test1() {
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
TreeSet<Integer> ts = new TreeSet<>(com);
}
// 简化1
@Test
public void test1() {
Comparator<Integer> com = (o1, o2) -> Integer.compare(o1, o2);
TreeSet<Integer> ts = new TreeSet<>(com);
}
// 简化2
@Test
public void test1() {
Comparator<Integer> com = Integer::compare;
TreeSet<Integer> ts = new TreeSet<>(com);
}
语法
- 箭头操作符 或 Lambda操作符:
->
左侧:Lambda表达式的参数列表
右侧:Lambda表达式中所执行的功能,即 Lambda体
- 无参数、无返回值
Runnable run = () -> System.out.println("Hello, Lambda!");
run.run();
- jdk 1.8 以前,匿名内部类里面引用的外部变量必须有final修饰,但在 jdk 1.8 开始,可省略final,但本质上仍是个final常量
// final int num = 0;
int num = 0;
Runnable run = () -> System.out.println("Hello, Lambda!" + num);
run.run();
- 有 1 个参数,无返回值
// Consumer<String> consumer = (x) -> System.out.println(x); // 可省略()
// Consumer<String> consumer = x -> System.out.println(x);
Consumer<String> consumer = System.out::println;
consumer.accept("test");
- 有 2 个及以上参数,并且 Lambda 体中有多条语句
单个参数可省略圆括号,多条语句必须要花括号,单条语句可省略花括号,单挑语句且有返回值还可以省略return
Comparator<Integer> comparator = (x, y) -> {
System.out.println(x + " " + y);
return Integer.compare(x, y);
};
- 参数列表的数据类型可省略
因为JVM可以通过上下文推断出数据类型
四大函数式接口
函数式接口:只有一个抽象方法的接口,可用
@FunctionalInterface
消费型接口:Consumer<T>
// 如何消费数据,无返回值
// 有 --消费--> 无
void accept(T t);
供给型接口:Supplier<T>
// 如何产生数据,返回指定格式的数据
// 无 --产生--> 有
T get();
@Test
public void test1() {
produceRandomArray(10, () -> new Random().nextInt(10));
}
private void produceRandomArray(Integer count, Supplier<Integer> sup) {
List<Integer> numList = new ArrayList<>();
for (int i = 0; i < count; i++) {
numList.add(sup.get());
}
numList.forEach(System.out::println);
}
函数型接口:Function<T, R>
// 源数据 --处理--> 新数据
R apply(T t);
断言型接口:Predicate<T>
boolean test(T t);
常见子接口
方法引用
若方法 Lambda 体中的内容已经有现成方法了,换言之, Lambda 表达式的参数列表与返回值类型,与函数式接口抽象方法的函数列表和返回值类型保持一致,那么就可以使用“方法引用”
对象名::实例方法名
类::静态方法名
类::实例方法名
比较特殊,条件更加苛刻! Lambda 表达式参数列表中的第一个参数是实例方法的调用者时,即
BiPredicate<String, String> biPredicate = (x, y) -> x.contains(y); Function<Employee, String> function4 = (e) -> e.getName();
可以使用ClassName :: Method
BiPredicate<String, String> biPredicate1 = String::contains; Function<Employee, String> function5 = Employee::getName;
// 1. 对象名::实例方法名
Consumer<String> consumer = System.out::println;
consumer.accept("test");
// 2. 类::静态方法名
// 相当于用等号左边的现有方法,实现了,等号左侧接口的抽象方法。有点像实现类对象指向抽象类引用
Comparator<Integer> comparator = Integer::compare;
BiFunction<Integer, Integer, Integer> biFunction = Integer::compare;
ToIntBiFunction<Integer, Integer> toIntBiFunction = Integer::compare;
// 3. 类::实例方法名
BiPredicate<String, String> biPredicate = (x, y) -> x.contains(y);
BiPredicate<String, String> biPredicate1 = String::contains;
BiPredicate<String, String> biPredicate2 = String::equals;
ToIntBiFunction<String, String> toIntBiFunction1 = String::compareTo;
Function<Employee, String> function4 = (e) -> e.getName();
Function<Employee, String> function5 = Employee::getName;
构造器引用
类命::new
// Lambda表达式(或者引用的方法)的参数列表与返回值类型,
// 要与函数式接口抽象方法的函数列表和返回值类型保持一致
// 调用无参构造器。因为Supplier的抽象方法是 T get();
Supplier<Employee> supplier = Employee::new;
// 调用有参构造器 Employee(String name)
Function<String, Employee> function = Employee::new;
// 调用有参构造器 Employee(String name, Integer age)
BiFunction<String, Integer, Employee> biFunction2 = Employee::new;
数组引用
Type[]::new
Function<Integer, String[]> function1 = (x) -> new String[x];
Function<Integer, String[]> function2 = String[]::new;
String[] strArr = function2.apply(10);
Stream API
什么是Stream
Stream的 3 个操作步骤
- 创建Stream
- 一个或多个中间操作,连接形成流水线
- 终止操作。除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
创建Stream
// 1. 通过Collection系列集合的 stream() 方法或 parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
// 2. 通过 Arrays 类中的静态方法 stream() 获取数组流
String[] strArr = new String[10];
Stream<String> stream1 = Arrays.stream(strArr);
// 3. 通过 Stream 类中的静态方法 of()
Stream<String> stream2 = Stream.of("aa", "bb", "cc");
// 4. 创建无限流,要有终止操作才有效果
// (1)迭代
Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2);
stream3.forEach(System.out::println); // 不停打印,停不下来
stream3.limit(10) // 中间操作
.forEach(System.out::println); // 终止操作
// (2)生成
Stream.generate(() -> new Random().nextInt(32))
.limit(32)
.forEach(System.out::println);
中间操作
筛选和切片
映射
List<Employee> emps = Arrays.asList(
new Employee("张三", 18, 9999.9),
new Employee("李四", 22, 6666.9),
new Employee("王五", 23, 8888.9),
new Employee("赵六", 26, 7777.9),
new Employee("田七", 20, 5555.9)
);
emps.stream()
.map(Employee::getName)
.forEach(System.out::println);
String[] nums = {
"10.2", "1.34", "3.14" };
List<String> list = Arrays.asList(nums);
DoubleStream doubleStream = list.stream()
.mapToDouble(Double::valueOf);
map()和flatMap()的区别
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
// map()本身返回Stream
// Test1::apartStr返回的是Stream<Character>
Stream<Stream<Character>> streamStream = list.stream()
.map(Test1::apartStr);
streamStream.forEach((stream) -> {
stream.forEach(System.out::println);
});
// ------------
Stream<Character> charStream =
list.stream().flatMap(Test1::apartStr);
charStream.forEach(System.out::println);
List<String> list = Arrays.asList("m-k-l-a", "1-3-5-7");
List<String> listNew = list.stream().flatMap(s -> {
// 将每个元素转换成一个stream
String[] split = s.split("-");
Stream<String> s2 = Arrays.stream(split);
return s2;
}).collect(Collectors.toList());
System.out.println("处理前的集合:" + list);
System.out.println("处理后的集合:" + listNew);
排序
自然排序:Comparable
定制排序:Comparator
List<String> list = Arrays.asList("aaa", "ccc", "bbb");
list.stream()
.sorted()
.forEach(System.out::println);
list.stream()
.sorted((s1, s2) -> {
return -s1.compareTo(s2);
})
.forEach(System.out::println);
终止操作
查找和匹配
- allMatch:检查是否匹配所有元素
- anyMatch:检查是否至少匹配一个元素
- noneMatch:检查是否没有匹配所有元素
- findFirst:返回第一个元素
- findAny:返回当前流中的任意元素
- count:返回流中元素的总个数
- max:返回流中最大值
- min:返回流中最小值
List<Employee> emps = Arrays.asList(
new Employee("张三", 18, 9999.9, Employee.Status.FREE),
new Employee("李四", 22, 6666.9, Employee.Status.WORK),
new Employee("王五", 23, 8888.9, Employee.Status.PLAY),
new Employee("赵六", 26, 7777.9, Employee.Status.FREE),
new Employee("田七", 20, 5555.9, Employee.Status.FREE)
);
// 全部 Employee 是 Free,就会返回true
boolean flag = emps.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
System.out.println(flag);
// 只要有 1 个 Employee 是 Free,就会返回true
boolean flag1 = emps.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
System.out.println(flag1);
// 全部 Employee 都不是 Free,就会返回 true
// 只要有一个 Employee 是 Free,就会返回 false
boolean flag2 = emps.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.FREE));
System.out.println(flag2);
Optional<Employee> emp1 = emps.stream()
.findFirst();
// System.out.println(emp1.get());
// 如果是数据较少,串行地情况下,一般会返回第一个结果
// 如果是并行的情况,那就不能确保是第一个。
Optional<Employee> emp2 = emps.parallelStream() // 并行流
.filter((e) -> e.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println(emp2.get());
//
Optional<Employee> maxEmp = emps.stream()
.max(Employee::compareTo);
System.out.println(maxEmp.get());
// 求最高工资
Optional<Double> maxSalary = emps.stream()
.map(Employee::getSalary)
.max(Double::compareTo);
System.out.println(maxSalary.get());
归约
// 求和
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// Integer::sum ----> (x, y) -> x+y
// 一开始把 0 当作 x,然后从集合中取出一个元素当作 y,求和为 1
// 然后把 1 再当作 x,再从集合中取出一个元素当作 y,求和为 3
// ...
Integer sum = list.stream()
// 有0作为初始值,不可能为空
.reduce(0, Integer::sum);
System.out.println(sum);
Optional<Integer> sum1 = list.stream()
// 没有初始值,可能为空,所以返回 Optional 对象
.reduce(Integer::sum);
System.out.println(sum1.get());
收集
// 拼接字符串
// reduce也可以实现拼接字符串,自行尝试
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
String collect = list.stream()
.map(String::valueOf)
.collect(Collectors.joining("-", "[", "]"));
System.out.println(collect);
// [1-2-3-4-5]
// 收集成指定集合
HashSet<String> collect1 = list.stream()
.map(String::valueOf)
.collect(Collectors.toCollection(HashSet::new));
System.out.println(collect1);
// 求平均值
Double avgSalary = emps.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avgSalary);
// 求和的3种方式
double sum = emps.stream()
.mapToDouble(Employee::getSalary)
.sum();
double sum2 = emps.stream()
.mapToDouble(Employee::getSalary)
.reduce(0, Double::sum);
double sum3 = emps.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
// 最大值、最小值,也可以收集
// 举一反三。。。略
// 一级分组
Map<Integer, List<Employee>> map = emps.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(map);
// 多级分组
Map<Integer, Map<String, List<Employee>>> mapMap = emps.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
// 返回值作为第二层Map的key
if (e.getAge() > 35) {
return "开除";
} else {
return "继续加班";
}
})));
System.out.println(mapMap);
// 分区(分成两部分)
Map<Boolean, List<Employee>> listMap = emps.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 6000));
System.out.println(listMap);
//总结(方便地获取多种数据统计方式的结果)
DoubleSummaryStatistics dss = emps.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getMax());
System.out.println(dss.getMin());
System.out.println(dss.getSum());
System.out.println(dss.getCount());
System.out.println(dss.getAverage());
并行流和顺序流
概念理解
stream
和parallelStream
的简单区分:stream
是顺序流,由主线程按顺序对流执行操作,而parallelStream
是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。如果流中的数据量足够大,并行流可以加快处速度。例如筛选集合中的奇数,两者的处理不同之处:
了解Fork/Join框架
利用Fork/Join的API计算和
public class ForkJoinCalculate extends RecursiveTask<Long> {
private static final long serialVersionUID = 1234567890L;
private long start;
private long end;
private static final long THRESHPLD = 10000;
public ForkJoinCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if (length <= THRESHPLD) {
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
} else {
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, end);
left.fork(); //拆分子任务 压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
right.fork();
return left.join() + right.join();
}
return null;
}
}
public class TestForkJoin {
/**
* ForkJoin 框架
*/
@Test
public void test01(){
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinCalculate task = new ForkJoinCalculate(0, 100000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getNano());
}
/**
* 普通 for循环
*/
@Test
public void test02(){
Instant start = Instant.now();
Long sum = 0L;
for (long i = 0; i < 100000000L; i++) {
sum += i;
}
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getNano());
}
}
Java8并行流例子
//串行流(单线程):切换为并行流 parallel()
//并行流:切换为串行流 sequential()
// 结果会溢出,不需要理会。运行时,注意观察CPU状态。运算会比普通for省时
LongStream.rangeClosed(0, 100000000L)
.parallel() //底层:ForkJoin
.reduce(0, Long::sum);
// 如果是数据较少,串行地情况下,一般会返回第一个结果
// 如果是并行的情况,那就不能确保是第一个。
// 注意,使用并行流时要考虑,并行操作对结果是否有影响
Optional<Employee> emp2 = emps.parallelStream() // 并行流
.filter((e) -> e.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println(emp2.get());
Optional类
Optional 类 (java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在用 Optional 可以更好的表达这个概念;并且可以避免空指针异常
常用方法:
- Optional.of(T t):创建一个 Optional 实例
- Optional.empty(T t):创建一个空的 Optional 实例
- Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则空实例
- isPresent():判断是否包含某值
- orElse(T t):如果调用对象包含值,返回该值,否则返回 t
- orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值
- map(Function f):如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty()
- flatmap(Function mapper):与 map 相似,要求返回值必须是 Optional
接口中的默认方法和静态方法
默认方法
public interface MyFun {
default String getName(){
return "interface";
}
}
public class ParentClass {
public String getName() {
return "ParentClass";
}
}
public class SubClass extends ParentClass implements MyFun {
}
// 测试调用
@Test
public void test() {
SubClass sub = new SubClass();
sub.getName(); // 输出 ParentClass
}
静态方法
接口中可以定义静态方法