Lambda
前言
当我们想要使用一个线程,我们其实并不想真的创建一个匿名内部类对象。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将 run 方法体内的代码传递给 Thread 类。
传统的使用一个线程
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
}
}
对于 Runnable 的匿名内部类用法,可以分析出几点内容:
- Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心;
- 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类;
- 为了省去定义一个 RunnableImpl 实现类的麻烦,不得不使用匿名内部类;
- 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
- 而实际上,似乎只有方法体才是关键所在
使用Lambda表达式写法
public class Test {
public static void main(String[] args) {
new Thread(()->System.out.println("多线程任务执行!")).start();
}
}
这段代码和上面的传统写法功能一样,但却是肉眼可见的简单!!
使用Lambda表达式的前提
那程序在满足什么条件的时候才可以使用Lambda表达式呢??
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。 无论是JDK内置的 Runnable 、Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
- 使用Lambda必须具有上下文推断。 也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
Lambda表达式格式
一个参数 一个箭头 一段代码
(参数类型 参数名)->{代码语句}//如果只有一条语句{}大括号也可以省略
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔
- 中间的一个箭头代表将前面的参数传递给后面的代码;
- 大括号内的语法与传统方法体要求基本一致。
Lambda表达式的省略规则
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参,则小括号可以省略;
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
通过这些省略规则,可以尝试简化 -----使用Collections的sort方法对集合字符串排序
Stream
前言
试想一下,如果希望对集合中的元素进行筛选过滤:
筛选出集合中所有姓张的人,在到所有姓张的人中筛选名字长度是3个字的!
传统的方法你可能会这样
import java.util.ArrayList;
import java.util.List;
public class Demo02NormalFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
} for (String name : shortList) {
System.out.println(name);
}
}
}
代码分析:
这段代码中含有三个循环,每一个作用不同:
- 首先筛选所有姓张的人;
- 然后筛选名字有三个字的人;
- 最后进行对结果进行打印输出。
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。
当我们使用Stream后的代码
import java.util.ArrayList;
import java.util.List;
public class Demo03StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
我丢!怎么可以这么简单 ┗( ▔, ▔ )┛
直接阅读代码的字面意思就可以完美展示这段代码的功能:获取流、过滤姓张、过滤长度为3、逐一打印。
获取流方式
获取的方法比较简单
- 所有的 Collection 集合都可以通过 stream 默认方法获取流;
- Stream 接口的静态方法 of 可以获取数组对应的流
java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
List集合举例
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
of 方法的参数其实是一个可变参数,所以支持数组。
举例
String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
Stream<String> stream = Stream.of(array);
常用方法
终结方法:
返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式
调用。终结方法包括 count 和 forEach 方法
非终结方法:
返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其
余方法均为非终结方法。)
方法名 | 方法作用 | 方法种类 | 是否支持链式调用 |
---|---|---|---|
count | 统计个数 | 终结 | 否 |
forEach | 逐一处理 | 终结 | 否 |
filter | 过滤 | 函数拼接 | 是 |
limit | 取用前几个 | 函数拼接 | 是 |
skip | 跳过前几个 | 函数拼接 | 是 |
concat | 组合 | 函数拼接 | 是 |
来个综合案例使用一下这些方法
/**
* 现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,依次进行以下若干操作步骤:
* 1. 第一个队伍只要名字为3个字的成员姓名;
* 2. 第一个队伍筛选之后只要前3个人;
* 3. 第二个队伍只要姓张的成员姓名;
* 4. 第二个队伍筛选之后不要前2个人;
* 5. 将两个队伍合并为一个队伍;
* 7. 打印整个队伍的信息。
*/
class Test{
public static void main(String[] args) {
//第一个大部队
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1,"张三","李四","王老五","赵四儿");
//第二个大部队
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2,"胡歌","李易峰","彭于晏","蔡徐坤");
//第一个队伍只要名字为3个字的成员姓名; 第一个队伍筛选之后只要前3个人;
Stream<String> stream1 = list1.stream();
Stream<String> streamOne = stream1.filter(s -> s.length() == 3).limit(3);
//第二个队伍只要姓张的成员姓名;第二个队伍筛选之后不要前2个人;
Stream<String> stream2 = list2.stream();
Stream<String> streamTwo = stream2.filter(s -> s.startsWith("张")).skip(2);
//将两个队伍合并为一个队伍; 打印整个队伍的信息。
Stream.concat(streamOne,streamTwo).forEach(s -> System.out.println(s));
}
}