Java函数式编程
Java函数式编程
Java 8引入了对函数式编程的支持。
Java8中引入的主要特性
- 1.Lambda表达式和函数式接口: Lambda表达式允许以更简洁的方式表达一个方法的实现。函数式接口,只定义了一个抽象方法的接口(使用@FunctionalInterface注解来标记此类接口),与Lambda表达式一起使用,以便可以将函数作为参数传递。
- 2.Stream API: Stream API 提供了一种用于对集合进行批量操作的方法,包括过滤、映射、化简等,可以更轻松地编写并行化和函数式风格的代码。
- 3.方法引用: 方法引用提供了一种更简洁地调用方法的方式,可以看作是Lambda表达式的进一步简化。
- 4.新的日期和时间API: 引入了全新的日期和时间API,改进了旧版Java日期和时间API的设计和易用性。
- 5.接口默认方法和静态方法: 接口可以包含默认方法实现和静态方法。默认方法允许接口中添加新的方法实现,而不会破坏现有的实现,并且可以通过接口实现类重写默认方法。
- 6.Optional类: Optional类是一个容器对象,可以包含或不包含一个非空值。可以减少代码中的空指针异常,并鼓励更好的错误处理方式
- 7.Nashorn JavaScript引擎: Nashorn是一个新的JavaScript引擎,它允许在Java程序中直接执行JavaScript代码。
- 8.并行数组排序: 引入了Arrays类的parallelSort()方法,可以对数组进行并行排序,以提高排序性能。
什么是函数式编程
函数式编程(Functional Programming,FP)是一种编程范式,它将计算视为数学函数的求值,并且避免了状态改变和可变数据。函数式编程强调使用纯函数(Pure Function)和不可变数据(Immutable Data)来进行编程。
纯函数(Pure Function): 纯函数是指对于相同的输入,总是产生相同的输出,并且没有副作用。它不依赖于程序的状态,也不改变程序的状态。这使得纯函数更容易推理、测试和并行化。
不可变数据(Immutable Data): 不可变数据是指一旦创建就不能被修改的数据结构。在函数式编程中,数据一旦创建就不会修改,而是通过创建新的数据结构来表示修改。
函数式编程的优势包含代码简洁、可读性强、并行化容易等,但也有限制与适用场景。在Java 8 中引入的Lambda表达式和Stream API就是函数式编程的一些体现,这些特性使得在Java中使用函数式编程风格更方便。
函数对象
在函数式编程中,函数对象(Function Object)通常是指可以作为一等公民的函数或Lambda表达式。在函数式编程范式中,函数与其他数据类型一样,可以被赋值给其他变量,可以作为参数传递给其他函数,也可以作为其他函数的返回值。
一等公民(First-Class Citizen): 一等公民是指函数与其他数据类型具有同等的地位。这意味着函数可以像整数、字符串或其他数据类型一样被创建、赋值给变量、作为参数传递给其他函数、作为其他函数的返回值,key存储在数据结构中。
函数对象的表现形式
两种表现形式都是一种语法糖
-
Lambda表达式
-
方法引用
Lambda表达式:
Lambda表达式是一种用于定义匿名函数的一种简洁语法。在Lambda表达式中允许你在需要函数对象的的地方快速定义一个函数,而不需要单独定义一个具名函数。
Lambda表达式的标准形式:
(形式参数) -> {代码块}
eg:
计算整型a,b的和(代码为单行时,"{}"可以省略)
(int a, int b) -> a + b;
计算整型a,b的和(代码为多行,不能省略"{}")
(int a, int b) -> { int c = a + b; return c; }
带有函数式接口的计算求和(当有上下文时,可以省略参数类型)
interface Lambda1{
int sum(int a, int b);
}
Lambda1 lambda = (a,b) -> a + b;
直接返回传参(只有一个参数时,可以省略"()")
a -> a;
方法引用:
方法引用是一种在支持Lambda表达式的编程语言中用于简化代码的语法特性。它允许你直接引用一个已经存在的方法,而不是编写一个Lambda表达式来执行相同的操作。
方法引用通常用于简化函数式编程中的代码,尤其是在使用Stream API等高级抽象时。它们可以提高代码的可读性并减少冗余。
方法引用符为 ::
eg:
调用Math类中的静态方法max求和
Math::max
对应的Lambda表达式为 (int a, int b) -> Math.max(a, b);
调用一个类的非静态方法获取成员变量值
Student::getName
对应Lambda表达式为 (Student stu) -> stu.getName
调用一个对象的非静态方法
System.out::println
对应Lambda表达式 (Object obj) -> System.out.println(obj);
调用一个类的构建方法
Student::new
对应Lambda表达式 () -> new Student();
函数式接口
函数式接口指的是只有一个抽象方法的接口。这样的接口可以用来表达Lambda表达式。通常使用@FunctionalInterface来表示函数式接口,用来检查接口是否有且仅有一个抽象方法。
// 添加一个函数式接口,入参为两个整型,返回为一个整型,对应接口的lambda表达式的逻辑为将两个整型求和并翻倍
@Slf4j
class DemoTestOne {
@FunctionalInterface
interface MathInterface {
int doubleSum(int a, int b);
}
@Test
void testOne() {
MathInterface mathInterface = (a, b) -> a + a + b + b;
log.info("{}", mathInterface.doubleSum(1, 2));
}
}
JDK中的函数式接口
1.Runnable
对应抽象方法: public abstract void run();
Lambda表达式: () -> void
2.Callable<V>
对应抽象方法: V call() throws Exception;
Lambda表达式: () -> V
3.Comparator<T>
对应抽象方法: int compare(T o1, T o2);
Lambda表达式: (T, T) -> int
4.Consumer<T>
对应抽象方法: void accept(T t);
Lambda表达式: (T) -> void
5.Function<T, R>
对应抽象方法: R apply(T t);
Lambda表达式: (T) -> R
6.Predicate<T>
对应抽象方法: boolean test(T t);
Lambda表达式: (T) -> boolean
7.Supplier<T>
对应抽象方法: T get();
Lambda表达式: () -> T
自定义函数式接口命名规范
名称 | 含义 |
---|---|
Consumer | 有参无返回值 |
Function | 有参有返回值 |
Predicate | 有参返回boolean |
Supplier | 无参有返回值 |
前缀 | 含义 |
---|---|
Unary | 一元,一个传参 |
Binary | 二元,两个传参 |
Ternary | 三元,三个传参 |
Quaternary | 四元,四个传参 |
//自定义一个函数式接口,四元传参有返回值,入参与返回值均为泛型,具体逻辑为拼接四个入参并以"-"符号作为分割符
@Slf4j
class DemoTestOne {
@FunctionalInterface
interface QuaternaryFunction<I, O> {
O apply(I a, I b, I c, I d);
}
@Test
void testOne() {
QuaternaryFunction<String, StringJoiner> quaternaryFunction = (a, b, c, d) -> {
StringJoiner stringJoiner = new StringJoiner("-");
stringJoiner.add(a);
stringJoiner.add(b);
stringJoiner.add(c);
stringJoiner.add(d);
return stringJoiner;
};
log.info("{}", quaternaryFunction.apply("this", "is", "a", "test").toString());
}
}
方法引用
将现有的方法调用转换为函数对象。方法引用是一种在支持Lambda表达式的编程语言中用于简化代码的语法特性。它允许你直接引用一个已经存在的方法,而不是编写一个Lambda表达式来执行相同的操作。
方法引用的形式
- 类名::静态方法
- 类名::非静态方法
- 对象::非静态方法
- 类名::new
- this::非静态方法
- super::非静态方法
1.类名::静态方法
逻辑:执行此静态方法
参数:静态方法的传参
eg:调用Math类的静态方法abs()和max()
Math::abs
调用的静态方法: Math.abs(n)
对应Lambda表达式: (n) -> Math.abs(n)
Math::max
调用的静态方法: Math.max(a,b)
对应的Lambda表达式: (a ,b) -> Math.max(a,b)
2.类名::非静态方法
逻辑:执行此非静态方法
参数:此类对象和非静态方法的传参
eg:调用一个类的非静态方法getId()和setId(),对应的对象名为stu
Student::getId
调用的静态方法: stu.getId()
Lambda表达式: (stu) -> stu.getId()
Student::setId
调用的静态方法: stu.setId(id)
Lambda表达式: (stu, id) -> stu.setId(id)
3.对象::非静态方法
逻辑: 执行此对象的非静态方法
参数: 非静态方法的传参
eg: 调用PrintStream类的对象的非静态方法println()
System.out::println
调用的静态方法: System.out.println(obj)
Lambda表达式: (obj) -> System.out.println(obj)
4.类名::new
逻辑: 执行此类的构造方法
参数: 执行此构造方法的传参
eg: 调用一个类的有参与无参构造方法(根据上下文来判断调用的是哪种构造方法)
Student::new
调用的静态方法
new Student()
Lambda表达式
() -> new Student()
Student::new
调用的静态方法
new Student(id)
Lambda表达式
(name) -> new Student(name)
5.this::非静态方法(不常用)
属于对象::非静态方法的特例,在类内部使用
逻辑: 执行此类对象的非静态方法
参数: 非静态方法的传参
6.super::非静态方法(不常用)
属于对象::非静态方法的特例,在类内部使用
逻辑: 执行此类对象父类的非静态方法
参数: 非静态方法的传参
闭包(Closure)
闭包就是能欧读取其他函数内部变量的函数。闭包是将函数内部和函数外部连接起来的桥梁。相当于函数对象和外界的变量绑定在一起,外界的变量必须是不可变数据。
// 使用闭包,依次执行打印执行任务
@Slf4j
class DemoTestOne {
@Test
void testOne() {
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
int k = i + 1;
//闭包,函数对象与外界变量k绑定在一起
Runnable task = () -> System.out.println(Thread.currentThread() + ":执行任务" + k);
list.add(task);
}
//创建单个线程的线程池
ExecutorService service = Executors.newSingleThreadExecutor();
//依次执行任务
for (Runnable task : list) {
service.submit(task);
}
}
}
柯里化(Curring)
柯里化是指将接收多个参数的函数转换成一系列接收一个参数的函数。柯里化结合闭包实现函数分步执行。
//将三个数据合并,数据不能一次得到,需要分为三次得到
@Slf4j
class DemoTestOne {
@FunctionalInterface
interface FunctionA {
FunctionB run(List<Integer> listA);
}
@FunctionalInterface
interface FunctionB {
FunctionC run(List<Integer> listB);
}
@FunctionalInterface
interface FunctionC {
List<Integer> run(List<Integer> listC);
}
static FunctionB stepOne() {
List<Integer> integerList = Arrays.asList(1, 2, 3);
FunctionA functionA = listA -> listB -> listC -> {
List<Integer> list = new ArrayList<>();
list.addAll(listA);
list.addAll(listB);
list.addAll(listC);
return list;
};
return functionA.run(integerList);
}
static FunctionC stepTwo(FunctionB functionB) {
List<Integer> integerList = Arrays.asList(4, 5);
return functionB.run(integerList);
}
static void stepThree(FunctionC functionC) {
List<Integer> integerList = Arrays.asList(6, 7);
List<Integer> resultList = functionC.run(integerList);
resultList.forEach(System.out::println);
}
@Test
void testOne() {
DemoTestOne.stepThree(stepTwo(stepOne()));
}
}
高阶函数
高阶函数是其他函数对象的使用者,高阶函数是指至少满足一下条件之一的函数:
1.接收一个或多个函数作为参数
2.返回一个函数作为结果
高阶函数的作用是将通用、复杂的逻辑隐含在高阶函数内部,将易变、未定的逻辑放在外部的函数对象中
//使用高阶函数对集合进行遍历
class DemoTestOne {
@Test
void testOne() {
List<Integer> list = List.of(1, 2, 3, 4, 5, 6);
traversal(list, (value) -> System.out.println(value));
}
//高阶函数,将一个函数对象作为传参
static <T> void traversal(List<T> list, Consumer<T> consumer) {
ListIterator<T> iterator = list.listIterator(list.size());
while (iterator.hasPrevious()) {
T value = iterator.previous();
consumer.accept(value);
}
}
}
使用高阶函数遍历二叉树
class DemoTestOne {
@Data
@AllArgsConstructor
public static class TreeNode {
/**
* 节点值
*/
private final int value;
/**
* 左子结点
*/
private final TreeNode left;
/**
* 右子节点
*/
private final TreeNode right;
//重写toString方法,直接返回节点值
public String toString() {
return String.valueOf(value);
}
}
enum Type {
PRE, IN, POST
}
@Test
void testOne() {
/**
* 生成一个二叉树,通过高阶函数实现对二叉树进行前中后序遍历
* 1
* / \
* 2 3
* / / \
* 4 5 6
* <p>
* 前序遍历 124356
* 中序变量 421536
* 后序遍历 425631
*
*/
TreeNode root = new TreeNode(1,
new TreeNode(2,
new TreeNode(4, null, null),
null),
new TreeNode(3,
new TreeNode(5, null, null),
new TreeNode(6, null, null)));
//递归二叉树遍历
System.out.println("============递归遍历=============");
System.out.println("===============前序==============");
traversalOne(root, Type.PRE, System.out::println);
System.out.println("===============中序==============");
traversalOne(root, Type.IN, System.out::println);
System.out.println("===============后序==============");
traversalOne(root, Type.POST, System.out::println);
//非递归二叉树遍历
System.out.println("============非递归遍历===========");
System.out.println("===============前序==============");
traversalTwo(root, Type.PRE, System.out::println);
System.out.println("===============中序==============");
traversalTwo(root, Type.IN, System.out::println);
System.out.println("===============后序==============");
traversalTwo(root, Type.POST, System.out::println);
System.out.println("=================================");
}
public static void traversalOne(TreeNode root, Type type, Consumer<TreeNode> consumer) {
if (root == null) {
return;
}
//前序处理
if (type == Type.PRE) {
consumer.accept(root);
}
traversalOne(root.left, type, consumer);
//中序处理
if (type == Type.IN) {
consumer.accept(root);
}
traversalOne(root.right, type, consumer);
//后序处理
if (type == Type.POST) {
consumer.accept(root);
}
}
public static void traversalTwo(TreeNode root, Type type, Consumer<TreeNode> consumer) {
//记录整个遍历过程
LinkedList<TreeNode> stack = new LinkedList<>();
//当前节点
TreeNode curr = root;
//最近一次遍历完的节点
TreeNode last = null;
while (curr != null || !stack.isEmpty()) {
//左子树未遍历完
if (curr != null) {
//添加遍历路线
stack.push(curr);
//前序处理
if (type == Type.PRE) {
consumer.accept(curr);
}
//向左子树移动
curr = curr.left;
}
//左子树遍历完
else {
//刚才遍历过的节点
TreeNode peek = stack.peek();
//没有右子节点
if (peek.right == null) {
if (type == Type.IN || type == Type.POST) {
consumer.accept(peek);
}
last = stack.pop();
}
//存在右子节点,已遍历
else if (peek.right == last) {
if (type == Type.POST) {
consumer.accept(peek);
}
last = stack.pop();
}
//存在右子节点,未遍历
else {
if (type == Type.IN) {
consumer.accept(peek);
}
curr = peek.right;
}
}
}
}
}
Stream API
Stream API是一种Java 8及以上版本中引入的用于处理数据集合的库。它提供了一种高效且易于使用的方式执行复杂的集合操作,如过滤、映射、排序、规约等。Stream API的核心是将集合转换为流,然后通过一系列中间操作和终结操作来处理这些流。
构建流
用已有数据构建出Stream对象
- 从集合中构建-集合.stream()
- 从数组构建-Arrays.stream(数组)
- 从对象构建-Stream.of(对象..)
class DemoTestOne {
@Test
void testOne() {
//从集合中构建
Set.of(1,2,3).stream().forEach(System.out::println);
Map.of("a",1,"b",2).entrySet().stream().forEach(System.out::println);
System.out.println("======================================");
//从数组中构建
int[] array = {1,2,3};
Arrays.stream(array).forEach(System.out::println);
System.out.println("======================================");
//从对象中构建
Stream.of(1,2,3).forEach(System.out::println);
}
}
过滤-filter
Stream<T> filter(Predicate<? super T> predicate);
函数式接口 Predicate 对应Lambda表达式 (T) -> boolean
保留流中逻辑为true的元素
// 从一组学生中筛选出年龄大于30的学生
class DemoTestOne {
@Data
@AllArgsConstructor
public static class Student {
private int id;
private String name;
private int age;
}
@Test
void testOne() {
Stream.of(
new Student(1,"小明",22),
new Student(2,"小红",34),
new Student(3,"李雷",50),
new Student(4,"小李",29),
new Student(5,"小刘",88)
).filter(student -> student.age>30)
.forEach(System.out::println);
}
}
映射-map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
函数式接口 Function 对应Lambda表达式 (T) -> R
// 将一组学生映射为学生姓名
class DemoTestOne {
@Data
@AllArgsConstructor
public static class Student {
private int id;
private String name;
private int age;
}
@Test
void testOne() {
Stream.of(
new Student(1,"小明",22),
new Student(2,"小红",34),
new Student(3,"李雷",50),
new Student(4,"小李",29),
new Student(5,"小刘",88)
).map(student -> ("学生:" + student.name))
.forEach(System.out::println);
}
}
降维-flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
函数式接口 Function 对应Lambda表达式 (T) -> R
//将两组集合降维至对象 List<Object> -> Object
class DemoTestOne {
@Data
@AllArgsConstructor
public static class Student {
private int id;
private String name;
private int age;
}
@Test
void testOne() {
Stream.of(
List.of(new Student(1,"小明",22),
new Student(2,"小红",34),
new Student(3,"李雷",50)),
List.of(new Student(4,"小李",29),
new Student(5,"小刘",88))
).flatMap(list->list.stream())
.forEach(System.out::println);
}
}
合并与截取
合并操作
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
合并两个数据类型相同的流为一个流
截取操作
Stream<T> skip(long n);
跳过n个数据,保留剩下的
Stream<T> limit(long maxSize);
保留n个数据,剩下的不要
default Stream<T> takeWhile(Predicate<? super T> predicate)
条件成立保留,一旦条件不成立,剩下的不要
default Stream<T> dropWhile(Predicate<? super T> predicate)
条件成立舍弃,一旦条件不成立,剩下的保留
//示例:
class DemoTestOne {
@Test
void testOne() {
/**
* 合并操作
*/
System.out.println("===================concate()=========================");
Stream<Integer> streamOne = Stream.of(1, 2, 3);
Stream<Integer> streamTwo = Stream.of(4, 5);
Stream<Integer> streamConcat = Stream.concat(streamOne, streamTwo);
streamConcat.forEach(System.out::println);
/**
* 截取操作
* skip(long n) 跳过n个数据,保留剩下的
* limit(long n) 保留n个数据,剩下的不要
* takeWhile(Predicate p) 条件成立保留,一旦条件不成立,剩下的不要
* dropWhile(Predicate p) 条件成立舍弃,一旦条件不成立,剩下的保留
*/
System.out.println("=====================skip()=============================");
Stream.of(1, 2, 3, 4, 5).skip(3).forEach(System.out::println);
System.out.println("=====================limit()============================");
Stream.of(1, 2, 3, 4, 5).limit(3).forEach(System.out::println);
System.out.println("=====================takeWhile()========================");
Stream.of(1, 2, 3, 4, 5).takeWhile(x -> x < 3).forEach(System.out::println);
System.out.println("=====================dropWhile()========================");
Stream.of(1, 2, 3, 4, 5).dropWhile(x -> x < 3).forEach(System.out::println);
}
}
查找与判断
查找:
filter(Predicate P).findAny()
找到任意一个元素
filter(Predicate P).findFirst()
找到第一个元素
注:查找方法后面可以接其他方法
.findFirst().orElse(-1) 如果未找到返回-1
.findAny.ifPresent(x -> System.out.println(x)) 如果找到则执行打印逻辑
判断:
anyMatch(Predicate p)
有一个元素满足就返回true
allMatch(Predicate p)
所有元素满足就返回true
noneMatch(Predicate p)
所有元素都不满足返回true
//示例:
class DemoTestOne {
@Test
void testOne() {
//查找操作
// 查找第一个为偶数的元素,如果没有返回-1
IntStream stream1 = IntStream.of(1, 2, 3, 4, 5);
System.out.println(stream1.filter(x -> ((x & 1) == 0)).findFirst().orElse(-1));
// 查找任意一个为偶数的元素,如果存在打印该元素
IntStream stream2 = IntStream.of(1, 2, 3, 4, 5);
stream2.filter(x -> ((x & 1) == 0)).findAny().ifPresent(x -> System.out.println(x));
//判断操作
// 有一个元素是偶数就返回true
IntStream stream3 = IntStream.of(1, 2, 5);
System.out.println(stream3.anyMatch(x -> ((x & 1) == 0)));
// 所有元素是偶数就返回true
IntStream stream4 = IntStream.of(2, 4, 6);
System.out.println(stream4.anyMatch(x -> ((x & 1) == 0)));
// 所有元素都不是偶数就返回true
IntStream stream5 = IntStream.of(1, 3, 5);
System.out.println(stream5.noneMatch(x -> ((x & 1) == 0)));
}
}
去重与排序
去重:
distinct()
排序:
sorted()
//示例:
class DemoTestOne {
@Data
@AllArgsConstructor
public static class Student {
private int id;
private String name;
private int age;
}
@Test
void testOne() {
//去重操作
IntStream.of(1, 2, 3, 4, 5, 2, 3, 4, 5).distinct().forEach(System.out::println);
//排序操作
//使用Lambda表达式 正排
System.out.println("================方式一===================");
Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50)
).sorted((a, b) -> a.getAge() < b.getAge() ? -1 : a.getAge() == b.getAge() ? 0 : 1).forEach(System.out::println);
//使用Lambda表达式 + Integer方法 正排
System.out.println("================方式二===================");
Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50)
).sorted((a, b) -> Integer.compare(a.getAge(), b.getAge())).forEach(System.out::println);
//使用比较器+Lambda表达式 正排
System.out.println("================方式三===================");
Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50)
).sorted(Comparator.comparing(h -> h.getAge())).forEach(System.out::println);
//使用比较器+方法引用 倒排
System.out.println("================方式四===================");
Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50)
).sorted(Comparator.comparing(Student::getAge).reversed()).forEach(System.out::println);
}
}
基本流
Stream API 有三种基本类型流
- IntStream
- LongStream
- DoubleStream
基本类型流可以使用的方法:
mapToObj(->obj) 基本类型流转换为obj流
boxed() 转换为Integer流
sum() 求和
count() 求元素个数
min() 求最小值
max() 求最大值
average() 求平均值
summaryStatistics() 综合count、sum、min、max、average
Stream流也可以使用方法转化为基本类型流
mapToInt(x->int) 转换为IntStream
mapToLong(x->long) 转换为LongStream
mapToDouble(x->double) 转换为DoubleStream
//示例:
class DemoTestOne {
@Data
@AllArgsConstructor
public static class Student {
private int id;
private String name;
private int age;
}
@Test
void testOne() {
//三种基本类型流
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
LongStream longStream = LongStream.of(1L, 3L, 5L, 7L);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);
//boxed方法
Stream<Long> longStream1 = longStream.boxed();
//summaryStatistics方法
IntSummaryStatistics intSummaryStatistics = intStream.summaryStatistics();
System.out.println(intSummaryStatistics.getCount());
System.out.println(intSummaryStatistics.getMax());
System.out.println(intSummaryStatistics.getSum());
System.out.println(intSummaryStatistics.getMin());
System.out.println(intSummaryStatistics.getAverage());
System.out.println("======================================");
//对象流转换为基本类型int流
Stream<Student> studentStream = Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50)
);
studentStream.mapToInt(Student::getAge).forEach(System.out::println);
}
}
生成流
不用现有数据生成Stream对象(基于基本类型流-IntStream)
- 简单生成-IntStream.range()
- 依赖上一个值生成当前值-IntStream.iterate()
- 不依赖上一个值生成当前值-IntStream.generate()
//示例:
class DemoTestOne {
@Test
void testOne() {
//range方法 含头不含尾
IntStream.range(1, 10).forEach(System.out::println);
//rangeClosed方法 含头也含尾
IntStream.rangeClosed(1, 10).forEach(System.out::println);
//iterate方法 + limit,按lambda表达式生成
IntStream.iterate(1, x -> x + 2).limit(5).forEach(System.out::println);
//iterate方法
IntStream.iterate(1, x -> x <= 5, x -> x + 2).forEach(System.out::println);
//generate方法 限制随机生成5个的介于0(包含)和指定边界100(不包含)之间的伪随机int值
IntStream.generate(()->ThreadLocalRandom.current().nextInt(100)).limit(5).forEach(System.out::println);
System.out.println("=======================");
ThreadLocalRandom.current().ints(5,0,100).forEach(System.out::println);
}
}
化简
实际上max、min方法底层是通过化简来实现的
化简:
reduce()
//示例:
class DemoTestOne {
@Data
@AllArgsConstructor
public static class Student {
private int id;
private String name;
private int age;
}
@Test
void testOne() {
//通过化简获得年龄最大的学生,默认值返回-1的学生
Stream<Student> stream1 = Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50)
);
Student maxAgeStu = stream1.reduce(new Student(-1, "", 0), (student1, student2) -> student1.getAge() > student2.getAge() ? student1 : student2);
System.out.println(maxAgeStu);
//通过化简,计算学生总数
Stream<Student> stream2 = Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50)
);
System.out.println(stream2.map(student -> 1).reduce(0, (a, b) -> a + b));
Stream<Student> stream3 = Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50)
);
System.out.println(stream3.count());
}
}
收集
收集操作将流中元素放到容器中
收集操作:
collect(() -> c,(c, x) -> void, ?)
()->c :创建容器c,容器一般是一个集合或者字符串
(c,x) -> void: 将元素x加入容器c
?:函数式接口(BiConsumer),一般用不到,给一个空逻辑,(a,b) -> {}
//示例:
class DemoTestOne {
@Test
void testOne() {
//流转换为List集合
Stream<String> stringStream1 = Stream.of("小红", "小明", "李雷");
List<String> stringList = stringStream1.collect(() -> new ArrayList<>(), (arrayList, x) -> arrayList.add(x), (a, b) -> {
});
Stream<String> stringStream2 = Stream.of("小红", "小明", "李雷");
List<String> stringList2 = stringStream2.collect(ArrayList::new, ArrayList::add, (a, b) -> {
});
stringList2.forEach(System.out::println);
//流转换为Set集合
Stream<String> stringStream3 = Stream.of("小红", "小明", "李雷");
Set<String> stringSet = stringStream3.collect(HashSet::new, HashSet::add, (a, b) -> {
});
stringSet.forEach(System.out::println);
//流转换为Map集合
Stream<String> stringStream4 = Stream.of("小红", "小明", "李雷");
Map<String, Integer> stringMap = stringStream4.collect(HashMap::new, (map, x) -> map.put(x, 1), (a, b) -> {
});
stringMap.forEach((k, v) -> System.out.println(k + ":" + v));
//流转换为StringBuilder
Stream<String> stringStream5 = Stream.of("小红", "小明", "李雷");
StringBuilder stringBuilder = stringStream5.collect(StringBuilder::new, StringBuilder::append, (a, b) -> {
});
System.out.println(stringBuilder);
//流转换为StringJoiner
Stream<String> stringStream6 = Stream.of("小红", "小明", "李雷");
StringJoiner stringJoiner = stringStream6.collect(() -> new StringJoiner(","), StringJoiner::add, (a, b) -> {
});
System.out.println(stringJoiner);
}
}
收集器操作:
collect(Collectors.xx)
Stream API 提供了收集器Collectors类用来创建不同类型的容器
Collectors.toList(): 转换为List集合形式的容器
Collectors.toSet(): 转换为Set集合形式的容器
Collectors.toMap(X -> k, y -> V): 转换为Map集合形式的容器,需要添加逻辑说明key与value如何赋值
Collectors.joining(x): 转换为String形式的容器,x表示分割符,不添加则不会添加分割符
//示例:
class DemoTestOne {
@Test
void testOne() {
//流转换为List集合
Stream<String> stringStream1 = Stream.of("小红", "小明", "李雷");
List<String> stringList = stringStream1.collect(Collectors.toList());
stringList.forEach(System.out::println);
//流转换为Set集合
Stream<String> stringStream2 = Stream.of("小红", "小明", "李雷");
Set<String> stringSet = stringStream2.collect(Collectors.toSet());
stringSet.forEach(System.out::println);
//流转换为Map集合
Stream<String> stringStream3 = Stream.of("小红", "小明", "李雷");
Map<String, Integer> stringMap = stringStream3.collect(Collectors.toMap(x -> x, x -> 1));
stringMap.forEach((k, v) -> System.out.println(k + ":" + v));
//流转换为String无分隔符
Stream<String> stringStream4 = Stream.of("小红", "小明", "李雷");
String s1 = stringStream4.collect(Collectors.joining());
System.out.println(s1);
//流转换为String有分割符
Stream<String> stringStream5 = Stream.of("小红", "小明", "李雷");
String s2 = stringStream5.collect(Collectors.joining(","));
System.out.println(s2);
}
}
下游收集器
基于收集操作,对流中数据进行分组并进行映射、过滤、降维等等操作后输入到容器中
collect(groupingBy(x->y), downstream)
x->y 以x对象的某个属性进行分组
downstream 下游收集器,将以x->y分组的数据输入到下游收集器中
//示例
class DemoTestOne {
@Data
@AllArgsConstructor
public static class Student {
private int id;
private String name;
private int age;
}
@Test
void testOne() {
// 根据姓名长度分组(key),只映射-mapping学生id(value)
Stream<Student> stream1 = Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50),
new Student(4, "落", 23),
new Student(5, "鲁智深", 76)
);
Map<Integer, List<Integer>> collect1 = stream1.collect(groupingBy(s -> s.getName().length(), mapping(s -> s.getId(), toList())));
collect1.forEach((k, v) -> System.out.println(k + ":" + v));
// 根据姓名长度分组(key),只保留-filter学生姓名大于49的(value)
Stream<Student> stream2 = Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50),
new Student(4, "落", 23),
new Student(5, "鲁智深", 76)
);
//Map<Integer, List<Student>> collect2 = stream2.collect(groupingBy(s -> s.getName().length(), filtering(s -> s.getAge() > 40, toList())));
//另一种方式,在分组之前进行过滤
Map<Integer, List<Student>> collect2 = stream2.filter(s -> s.getAge() > 40).collect(groupingBy(s -> s.getName().length(), toList()));
collect2.forEach((k, v) -> System.out.println(k + ":" + v));
// 根据姓名长度分组(key),只保留人名,并将人名拆分(value)
Stream<Student> stream3 = Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50),
new Student(4, "落", 23),
new Student(5, "鲁智深", 76)
);
Map<Integer, List<String>> collect3 = stream3.collect(groupingBy(s -> s.getName().length(), flatMapping(h -> h.getName().chars().mapToObj(Character::toString), toList())));
collect3.forEach((k, v) -> System.out.println(k + ":" + v));
// 根据姓名长度分组(key),分组后求每组的元素个数
Stream<Student> stream4 = Stream.of(
new Student(1, "小明", 22),
new Student(2, "小红", 34),
new Student(3, "李雷", 50),
new Student(4, "落", 23),
new Student(5, "鲁智深", 76)
);
Map<Integer,Long> collect4 = stream4.collect(groupingBy(s->s.getName().length(),counting()));
collect4.forEach((k, v) -> System.out.println(k + ":" + v));
//除此之外还有求分组后最高-maxBy(),最低-minBy(),求和-summingInt(),平均值方法-averagingDouble(),等等方法的下游收集器
}
}
流的特性
1.一次使用
2.两类操作(中间操作-懒惰,终结操作-急切),中间操作不会直接执行,而是等到出现终结操作时,才会一次性全部执行
//示例:创建一个流
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
intStream.filter(x -> x > 2) // 中间操作-不立刻执行
.map(x -> x + 1) // 中间操作-不立刻执行
.forEach(System.out::println); // 终结操作-立即执行,无法多次执行且执行之后无法再调用该流
流的方法总结
创建操作
api | 说明 |
---|---|
Arrays.stream(数组) | 根据数组构建 |
Collection.stream() | 根据数组构建 |
Stream.of(对象1,对象2...) | 根据对象构建 |
IntStream.range(a,b) | 根据范围生成(含a不含b) |
IntStream.rangeClosed(a,b) | 根据范围生成(含a也含b) |
IntStream.iterate(s,p->c) | s初始值,p->c 数值变更逻辑 |
IntStream.generate(() -> c) | 在指定区间随机生成 |
Stream.concat(流1,流2) | 合并两个流 |
中间操作
api | 说明 |
---|---|
stream.skip(n) | 舍弃n个 |
stream.limit(n) | 保留n个 |
stream.dropWhile(x->boolean) | 舍弃,直到条件不满足 |
stream.takeWhile(x->boolean) | 保留,直到条件不满足 |
stream.map(x->y) | 将x映射为y |
stream.flatMap(x->substream) | 将x降维为另一种流 |
stream.mapToInt(x->int) | 将流转换为IntStream |
stream.mapToLong | 将流转换为LongStream |
stream.distinct() | 去重 |
stream.sort((a,b)->int) | 排序,a与b比较,负a小,0相等,正 a大 |
终结操作
api | 说明 |
---|---|
stream.findFirst() | 找到第一个符合条件的元素,返回Optional类 |
stream.findAny() | 找到任意一个符合条件的元素,返回Optional类 |
stream.anyMatch(x->boolean) | 任意一个满足即可返回true |
stream.allMatch(x->boolean) | 所有都满足才返回true |
stream.noneMatch(x->boolean) | 所有都不满足才返回true |
stream.forEach(x->void) | 消费 |
stream.forEachOrdered(x->void) | 按序消费 |
stream.reduce(init,(p,x)->r,(r1,r2)->r) | 化简 |
stream.min((a,b)->int) | 求最小值 |
stream.max((a,b)->int) | 求最大值 |
stream.count() | 求个数 |
stream.toArray() | 收集为数组 |
stream.collect(()->c,(c,x)->void,(a,b)->{}) | 收集到容器 |
并行流
并行流方法: parallel
并行流只适合大数据量的情况下使用,数据量少不需要考虑使用此方法
//示例
使用并行流方法,打印一个基本类型流
class DemoTestOne {
@Test
void testOne() {
IntStream.of(1, 2, 3, 4, 5)
.parallel() //并行流方法
.forEach(System.out::println);
}
}
函数式编程应用场景
- 数据统计分析(Stream API)
- WEB开发(路由请求)
- 框架设计(函数对象)
- 异步处理(CompletableFuture)