day14【Lambda表达式、Stream流】
第一章 Lambda表达式
1.1函数式编程思想概述
面向对象编程思想
面向对象强调的是对象 , “必须通过对象的形式来做事情”,相对来讲比较复杂,有时候我们只是为了做某件事情而不得不创建一个对象 , 例如线程执行任务,我们不得不创建一个实现Runnable接口对象,但我们真正希望的是将run方法中的代码传递给线程对象执行
函数编程思想
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。例如线程执行任务 , 使用函数式思想 , 我们就可以通过传递一段代码给线程对象执行,而不需要创建任务对象
- 函数式编程思想强调做什么,而不是以什么形式做,也就是直接传入一段代码,不需要创建对象
1.2Lambda表达式的体验
实现Runnable接口的方式创建线程执行任务
实现类:
1.创建一个实现类,实现Runnable接口
2.在实现类中,重写run()方法,把任务放入run()方法中
3.创建实现类对象
4.创建Thread线程对象,传入实现类对象
5.使用线程对象调用start()方法,启动并执行线程
总共需要5个步骤,一步都不能少,为什么要创建实现类,为了得到线程的任务
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现的方法创建线程的任务执行了...");
}
}
public class Demo {
public static void main(String[] args) {
// 实现类的方式:
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
}
}
匿名内部类方式创建线程执行任务
匿名内部类:
1.创建Thread线程对象,传入Runnable接口的匿名内部类
2.在匿名内部类中重写run()方法,把任务放入run()方法中
3.使用线程对象调用start()方法,启动并执行线程
总共需要3个步骤,一步都不能少,为什么要创建Runnable的匿名内部类类,为了得到线程的任务
public class Demo {
public static void main(String[] args) {
// 匿名内部类的方式:
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类的方式创建线程的任务执行了");
}
});
t2.start();
}
}
Lambda方式创建线程执行任务
以上2种方式都是通过Runnable接口的实现类对象,来传入线程需要执行的任务(面向对象编程)
思考: 是否能够不通过Runnable接口的实现类对象来传入任务,而是直接把任务传给线程???
Lambda表达式的概述:
它是一个JDK8开始一个新语法。它是一种“代替语法”——可以代替我们之前编写的“面向某种接口”编程的情况
public class Demo {
public static void main(String[] args) {
// 体验Lambda表达式的方式:
Thread t3 = new Thread(()->{System.out.println("Lambda表达式的方式");});
t3.start();
}
}
- Lambda表达式的作用就是简化代码,省略了面向对象中类和方法的书写。
1.3Lambda表达式的格式
省略规则
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参数,则小括号可以省略;
- 如果大括号内有且仅有一条语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
案例演示
- 线程案例演示
public class Demo_线程演示 {
public static void main(String[] args) {
//Lambda表达式省略规则
Thread t2 = new Thread(()-> System.out.println("执行了"));
t2.start();
}
}
- 比较器案例演示
public class Demo_比较器演示 {
public static void main(String[] args) {
//比较器
ArrayList<Integer> list = new ArrayList<>();
//添加元素
list.add(324);
list.add(123);
list.add(67);
list.add(987);
list.add(5);
System.out.println(list);
//Lambda表达式
Collections.sort(list, ( o1, o2)-> o2 - o1);
//打印集合
System.out.println(list);
}
}
1.4Lambda的前提条件和表现形式
Lambda的前提条件
-
使用Lambda必须具有接口,且要求接口中的抽象方法有且仅有一个。(别的方法没有影响)
-
使用Lambda必须具有上下文推断。
-
如果一个接口中只有一个抽象方法,那么这个接口叫做是函数式接口。
@FunctionalInterface这个注解 就表示这个接口是一个函数式接口
-
Lambda的表现形式
- 变量形式
- 参数形式
- 返回值形式
public class Demo {
public static void main(String[] args) {
/*
Lambda表达式其实就是用来替换函数式接口的对象
Lambda表达式的标准格式
Lambda表达式的省略格式
Lambda的几种使用形式: 使用场景
1.变量的形式:变量的类型为函数式接口类型,那么可以赋值一个Lambda表达式
2.参数的形式:方法的形参类型为函数式接口类型,那么就可以传入一个Lambda表达式
3.返回值的形式:方法的返回值类型为函数式接口类型,那么就可以返回一个Lambda表达式
*/
// 变量的形式:
Runnable r = ()->{System.out.println("变量的形式");};// 需要Runnable函数式接口的对象,所以可以使用Lambda表达式替换
r.run();
// 参数的形式:
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"赵丽颖","马尔扎哈","杨颖","波多野结衣");
System.out.println("排序之前:"+list);// 排序之前:[赵丽颖, 马尔扎哈, 杨颖, 波多野结衣]
Collections.sort(list,(String o1,String o2)->{return o2.length() - o1.length();});
System.out.println("排序之后:"+list);// 排序之后:[波多野结衣, 马尔扎哈, 赵丽颖, 杨颖]
}
// 返回值的形式: 方法的返回值类型为函数式接口类型,那么就可以返回一个Lamdba表达式
public static Comparator<String> getComparator(){
return (String o1,String o2)->{return o2.length() - o1.length();};
}
public static Runnable getRunnable(){
return ()->{System.out.println("====");};
}
}
第二章 Stream
2.1Stream流的引入
例如: 有一个List集合,要求:
- 将List集合中姓张的的元素过滤到一个新的集合中
- 然后将过滤出来的姓张的元素,再过滤出长度为3的元素,存储到一个新的集合中
传统方式操作集合
public class Demo {
public static void main(String[] args) {
// 传统方式操作集合:
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张杰");
list.add("张三丰");
// 1.将List集合中姓张的的元素过滤到一个新的集合中
// 1.1 创建一个新的集合,用来存储所有姓张的元素
List<String> listB = new ArrayList<>();
// 1.2 循环遍历list集合,在循环中判断元素是否姓张
for (String e : list) {
// 1.3 如果姓张,就添加到新的集合中
if (e.startsWith("张")) {
listB.add(e);
}
}
// 2.然后将过滤出来的姓张的元素,再过滤出长度为3的元素,存储到一个新的集合中
// 2.1 创建一个新的集合,用来存储所有姓张的元素并且长度为3
List<String> listC = new ArrayList<>();
// 2.2 循环遍历listB集合,在循环中判断元素长度是否为3
for (String e : listB) {
// 2.3 如果长度为3,就添加到新的集合中
if(e.length() == 3){
listC.add(e);
}
}
// 3.打印所有元素---循环遍历
for (String e : listC) {
System.out.println(e);
}
}
}
Stream流操作集合
public class Demo {
public static void main(String[] args) {
// 体验Stream流:
list.stream().filter(e->e.startsWith("张")).filter(e->e.length()==3).forEach(e-> System.out.println(e));
System.out.println(list);
}
}
直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。
2.2流式思想概述
流式思想: 待会学了常用方法后验证
搭建好函数模型,才可以执行 函数模型: 一定要有终结的方法,没有终结的方法,这个函数模型是不会执行的
- Stream流的操作方式也是流动操作的,也就是说每一个流都不会存储元素
- 一个Stream流只能操作一次,不能重复使用
- Stream流操作不会改变数据源
2.3获取流方式
- 根据Collection获取流
- Collection接口中有一个stream()方法,可以获取流 , default Stream stream():获取一个Stream流
- 通过List集合获取:
- 通过Set集合获取
根据Map获取流
-
使用所有键的集合来获取流
-
使用所有值的集合来获取流
-
使用所有键值对的集合来获取流
根据数组获取流
-
Stream流中有一个static Stream of(T… values)
- 通过数组获取:
- 通过直接给多个数据的方式
案例演示
public class Test {
public static void main(String[] args) {
/*
获取流的方式:
通过集合或者数组:
根据Collection获取流:
使用Collection中的方法: default Stream<E> stream();返回一个Stream流对象
通过List集合获取流
通过Set集合获取流
根据Map获取流
通过获取Map集合的键来获取流
通过获取Map集合的值来获取流
通过获取Map集合的键值对对象来获取流
根据数组获取流
Stream接口中的of方法: static <T> Stream<T> of(T... values)
通过直接传入多个元素获取流
Stream接口中的of方法: static <T> Stream<T> of(T... values)
案例演示
*/
// 通过List集合获取流
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张杰");
list.add("张三丰");
Stream<String> stream1 = list.stream();
// 通过Set集合获取流
Set<String> set = new HashSet<>();
set.add("张无忌");
set.add("周芷若");
set.add("赵敏");
set.add("张杰");
set.add("张三丰");
Stream<String> stream2 = set.stream();
System.out.println("=====================");
Map<String,String> map = new HashMap<>();
map.put("谢霆锋","张柏芝");
map.put("王宝强","马蓉");
map.put("贾乃亮","李小璐");
map.put("陈羽凡","白百何");
// 通过获取Map集合的键来获取流
Set<String> keys = map.keySet();
Stream<String> stream3 = keys.stream();
// 通过获取Map集合的值来获取流
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
// 通过获取Map集合的键值对对象来获取流
Set<Map.Entry<String, String>> entrySet = map.entrySet();
Stream<Map.Entry<String, String>> stream5 = entrySet.stream();
System.out.println("==========================");
// 通过数组获取流
String[] arr = {"谢霆锋","张柏芝","陈羽凡","白百何"};
Stream<String> stream6 = Stream.of(arr);
// 通过直接传入多个元素获取流
Stream<String> stream7 = Stream.of("谢霆锋", "张柏芝", "陈羽凡", "白百何");
}
}
2.4常用方法
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
- 终结方法:返回值类型不再是
Stream
接口自身类型的方法,因此不再支持类似StringBuilder
那样的链式调用。本小节中,终结方法包括count
和forEach
方法。 - 非终结方法:返回值类型仍然是
Stream
接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。)
函数拼接与终结方法
在上述介绍的各种方法中,凡是返回值仍然为Stream
接口的为函数拼接方法,它们支持链式调用;而返回值不再为Stream
接口的为终结方法,不再支持链式调用。如下表所示:
方法名 | 方法作用 | 方法种类 | 是否支持链式调用 |
---|---|---|---|
count | 统计个数 | 终结 | 否 |
forEach | 逐一处理 | 终结 | 否 |
filter | 过滤 | 函数拼接 | 是 |
limit | 取用前几个 | 函数拼接 | 是 |
skip | 跳过前几个 | 函数拼接 | 是 |
map | 映射 | 函数拼接 | 是 |
concat | 组合 | 函数拼接 | 是 |
备注:本小节之外的更多方法,请自行参考API文档。
forEach : 逐一处理
虽然方法名字叫forEach
,但是与for循环中的“for-each”昵称不同,该方法并不保证元素的逐一消费动作在流中是被有序执行的。
void forEach(Consumer<? super T> action);
该方法接收一个Consumer
接口函数,会将每一个流元素交给该函数进行处理。例如:
import java.util.stream.Stream;
public class Demo12StreamForEach {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("谢霆锋", "张柏芝", "陈羽凡", "白百何");
// 对stream1这个流中的每个元素进行打印输出
// stream1.forEach((String e)->{System.out.println(e);});
stream1.forEach( e->System.out.println(e));
}
}
count:统计个数
正如旧集合Collection
当中的size
方法一样,流提供count
方法来数一数其中的元素个数:
long count();
该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用:
public class Demo09StreamCount {
public static void main(String[] args) {
// Stream<String> stream1 = Stream.of("谢霆锋", "张柏芝", "陈羽凡", "白百何");
// long count = stream1.count();
// 链式编程
long count = Stream.of("谢霆锋", "张柏芝", "陈羽凡", "白百何").count();
System.out.println("流中元素的个数: "+count);
}
}
filter:过滤
可以通过filter
方法将一个流转换成另一个子集流。方法声明:
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个Predicate
函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
基本使用
Stream流中的filter
方法基本使用的代码如:
public class Demo07StreamFilter {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s -> s.startsWith("张"));
}
}
在这里通过Lambda表达式来指定了筛选的条件:必须姓张。
limit:取用前几个
limit
方法可以对流进行截取,只取用前n个。方法签名:
Stream<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:
import java.util.stream.Stream;
public class Demo10StreamLimit {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.limit(2);
System.out.println(result.count()); // 2
}
}
skip:跳过前几个
如果希望跳过前几个元素,可以使用skip
方法获取一个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:
import java.util.stream.Stream;
public class Demo11StreamSkip {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 1
}
}
map:映射
如果需要将流中的元素映射到另一个流中,可以使用map
方法。方法签名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个Function
函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
基本使用
Stream流中的map
方法基本使用的代码如:
import java.util.stream.Stream;
public class Demo08StreamMap {
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(s->Integer.parseInt(s));
}
}
这段代码中,map
方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为Integer
类对象)。
concat:组合
如果有两个流,希望合并成为一个流,那么可以使用Stream
接口的静态方法concat
:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
备注:这是一个静态方法,与
java.lang.String
当中的concat
方法是不同的。
该方法的基本使用代码如:
import java.util.stream.Stream;
public class Demo12StreamConcat {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张无忌");
Stream<String> streamB = Stream.of("张翠山");
Stream<String> result = Stream.concat(streamA, streamB);
}
}
2.5实操-- Stream综合案例
需求
现在有两个ArrayList
集合存储队伍当中的多个成员姓名,要求使用Stream流,依次进行以下若干操作步骤:
- 第一个队伍只要名字为3个字的成员姓名;
- 第一个队伍筛选之后只要前3个人;
- 第二个队伍只要姓张的成员姓名;
- 第二个队伍筛选之后不要前2个人;
- 将两个队伍合并为一个队伍;
- 根据姓名创建
Person
对象; - 打印整个队伍的Person对象信息。
两个队伍(集合)的代码如下:
两个队伍(集合)的代码如下:
public class DemoArrayListNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
// ....
}
}
分析
- 可以使用Stream流的操作,来简化代码
实现
Person
类的代码为:
public class Person {
public String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
public class Test {
public static void main(String[] args) {
/*
需求
现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用Stream流,依次进行以下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;
2. 第一个队伍筛选之后只要前3个人;
3. 第二个队伍只要姓张的成员姓名;
4. 第二个队伍筛选之后不要前2个人;
5. 将两个队伍合并为一个队伍;
6. 根据姓名创建Person对象;
7. 打印整个队伍的Person对象信息。
*/
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
// 1. 第一个队伍只要名字为3个字的成员姓名; filter
// 2. 第一个队伍筛选之后只要前3个人; limit
Stream<String> stream1 = one.stream().filter((String name) -> {
return name.length() == 3;
}).limit(3);
// 3. 第二个队伍只要姓张的成员姓名;filter
// 4. 第二个队伍筛选之后不要前2个人;skip
Stream<String> stream2 = two.stream().filter((String name) -> {
return name.startsWith("张");
}).skip(2);
// 5. 将两个队伍合并为一个队伍;
// 6. 根据姓名创建Person对象; map String-->Person
// 7. 打印整个队伍的Person对象信息。
Stream.concat(stream1,stream2).map((String name)->{
/* Person p = new Person(name);
return p;
*/
return new Person(name);
}).forEach(p-> System.out.println(p));
}
}
运行效果完全一样:
Person{name='宋远桥'}
Person{name='苏星河'}
Person{name='洪七公'}
Person{name='张二狗'}
Person{name='张天爱'}
Person{name='张三'}
2.6收集Stream结果
收集到集合中
- Stream流中提供了一个方法,可以把流中的数据收集到单列集合中
- <R,A> R collect(Collector<? super T,A,R> collector): 把流中的数据收集到单列集合中
- 参数Collector<? super T,A,R>: 决定把流中的元素收集到哪个集合中
- 返回值类型是R,也就是说R指定为什么类型,就是收集到什么类型的集合
- 参数Collector如何得到? 使用java.util.stream.Collectors工具类中的静态方法:
- public static Collector<T, ?, List> toList():转换为List集合。
- public static Collector<T, ?, Set> toSet():转换为Set集合。
- <R,A> R collect(Collector<? super T,A,R> collector): 把流中的数据收集到单列集合中
下面是这两个方法的基本使用代码:
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Demo15StreamCollect {
public static void main(String[] args) {
Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
}
}
收集到数组中
Stream提供toArray
方法来将结果放到一个数组中,返回值类型是Object[]的:
Object[] toArray();
其使用场景如:
import java.util.stream.Stream;
public class Demo16StreamArray {
public static void main(String[] args) {
Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
Object[] objArray = stream.toArray();
}
}
第三章 总结
-
能够掌握Lambda表达式的标准格式与省略格式
Lambda是新式的语法,是函数式接口匿名内部类的替代品
格式: (参数列表)->{ 功能代码 }
小括号中的内容要和函数式接口中的抽象方法参数列表一致
大括号中的功能代码就和实现函数式接口中的抽象方法的方法体一致
使用前提:
接口必须是函数式接口,也就是接口中有且仅有一个抽象方法
Lambda使用套路:
1.确定Lambda表达式的格式 ()->{}
2.根据函数式接口的抽象方法 来填充()和{}里面的内容
省略规则:
1. 小括号中的参数类型可以省略
2. 小括号中有且仅有一个参数,那么小括号也可以省略
3. 大括号中有且仅有一条语句,那么大括号,return,语句分号可以一起省略 -
能够通过集合、映射或数组方式获取流
使用Collection集合的stream()方法
使用Stream流的静态方法of() -
能够掌握常用的流操作
count() 统计元素个数
forEach() 逐一消费元素
filter() 过滤
limit() 取前几个
skip() 跳过前几个
map() 映射
concat() 拼接 -
能够将流中的内容收集到集合和数组中
收集到集合: 使用Stream流的collect()方法
收集到数组: 使用Stream流的toArray()方法练习: 常用的流操作 Lambda表达式的标准格式和省略格式
理解: 函数式编程思想 Lambda表达式的体验 Stream流的引入 流式思想