如果你不懂函数式编程,公司的项目代码会看的非常难受!!!
1、创建stream流
在Java中,你可以使用不同的方式来创建Stream流,具体取决于你的数据源。下面详细介绍了三种常见的创建Stream的方式:
1. 创建单列集合的Stream
如果你有一个单列集合,如List、Set等,可以使用以下方式创建一个Stream:
List<Integer> numbersList = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> streamFromCollection = numbersList.stream();
这里,stream()
方法是Collection
接口的默认方法,它返回一个顺序流(有序流)。你可以对numbersList
中的元素执行各种操作,如过滤、映射、排序等。
2. 创建数组的Stream
如果你有一个数组,可以使用以下两种方式创建一个Stream:
使用 Arrays.stream(数组)
int[] numbersArray = {1, 2, 3, 4, 5};
IntStream streamFromArray = Arrays.stream(numbersArray);
这将创建一个特定类型的Stream,例如IntStream
,DoubleStream
或LongStream
,具体取决于数组元素的类型。你可以使用streamFromArray
对数组元素执行各种操作。
使用 Stream.of(元素1, 元素2, ...)
来创建
Stream<String> stringStream = Stream.of("apple", "banana", "cherry");
这将创建一个泛型的Stream,你可以在其中包含任何类型的元素。在这个示例中,我们创建了一个Stream<String>
,包含了字符串元素。
3. 创建双列集合的Stream
如果你有一个双列集合,如Map
,你可以通过以下方式创建一个Stream:
首先,你可以使用entrySet()
方法将Map
转换为键值对的集合(Set<Map.Entry<K, V>>
),然后使用stream()
方法创建一个Stream:
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 28);
Stream<Map.Entry<String, Integer>> streamFromMap = map.entrySet().stream();
这将创建一个包含Map.Entry<String, Integer>
类型的Stream,其中键值对的键是字符串,值是整数。你可以对这个Stream执行各种操作来处理键值对。
另外,如果你只对Map
的键或值感兴趣,你可以使用keySet().stream()
或values().stream()
方法来创建Stream,分别得到键的Stream或值的Stream。
这三种方式可以让你根据不同的数据源来创建Stream,然后使用Stream的各种操作来处理数据,使代码更加流畅和具有表现力。
2、Stream流的中间操作
Stream流的中间操作是一系列可以在流上进行的操作,它们通常不会立即触发流的遍历或计算,而是构建了一个操作流水线,等到终止操作执行时才会真正处理数据。以下是一些常见的Stream流中间操作以及示例:
1、filter(Predicate<T> predicate):根据指定的条件过滤流中的元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0);
在这个例子中,filter
操作保留了列表中的偶数
2、map
是一个常用的中间操作,它用于将流中的每个元素映射为另一个元素。map
操作可以帮助你对流中的元素进行转换、提取或处理。以下是一些常见的 map
操作的用法:
转换元素类型:将流中的元素从一种类型映射为另一种类型。
List<String> words = Arrays.asList("hello", "world");
List<Integer> wordLengths = words.stream()
.map(String::length) // 将字符串映射为字符串长度
.collect(Collectors.toList());
这个示例中,map
操作将字符串映射为它们的长度,生成一个包含整数的列表。
提取对象属性:从对象中提取特定属性或字段。
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 28)
);
List<String> names = people.stream()
.map(Person::getName) // 提取每个人的名字
.collect(Collectors.toList());
在这个示例中,map
操作提取了每个人的名字,生成一个包含名字的列表。
执行复杂计算:执行复杂的计算来映射元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squaredNumbers = numbers.stream()
.map(n -> n * n) // 将每个数字映射为其平方
.collect(Collectors.toList());
这个示例中,map
操作将每个数字映射为其平方,生成一个包含平方值的列表。
构建新对象:使用映射操作构建新的对象。
List<Integer> ids = Arrays.asList(1, 2, 3);
List<User> users = ids.stream()
.map(id -> new User(id, "User " + id)) // 根据ID构建用户对象
.collect(Collectors.toList());
在这个示例中,map
操作根据ID构建了新的用户对象,生成一个包含用户对象的列表。
字符串操作:对字符串进行操作,如拼接、转换大小写等。
List<String> words = Arrays.asList("apple", "banana", "cherry");
List<String> uppercaseWords = words.stream()
.map(String::toUpperCase) // 将每个单词转换为大写
.collect(Collectors.toList());
这个示例中,map
操作将每个单词转换为大写形式,生成一个包含大写单词的列表。
这些是 map
操作的一些常见用法示例。它允许你对流中的元素进行灵活的转换和处理,以满足不同的需求
3、flatMap的用法
flatMap
是一个非常强大和灵活的中间操作,它用于将一个流中的每个元素映射成一个新的流,并将这些新流合并成一个单一的流。这个操作通常用于处理嵌套的数据结构或将多个流合并成一个流。
下面提供一些关于 flatMap
的常见用法和示例:
1. 处理嵌套的数据结构
假设你有一个包含多个列表的列表,你想将所有的元素合并成一个单一的列表。这时可以使用 flatMap
:
List<List<Integer>> nestedLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
List<Integer> flattenedList = nestedLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
在这个示例中,flatMap
将每个内部列表转化为一个流,并将这些流合并成一个单一的流。最终,flattenedList
包含了所有的整数元素。
2. 处理Optional对象
如果你有一个包含 Optional
对象的流,并且想要获取其中的非空值,可以使用 flatMap
:
List<Optional<String>> nameOptionals = Arrays.asList(
Optional.of("Alice"),
Optional.empty(),
Optional.of("Bob"),
Optional.of("Charlie")
);
List<String> names = nameOptionals.stream()
.flatMap(optional -> optional.map(Stream::of).orElseGet(Stream::empty))
.collect(Collectors.toList());
在这个示例中,flatMap
用于将 Optional
对象中的非空值提取出来,并合并成一个单一的流,最终获得了所有非空的名字。
3. 从多个流中合并元素
假设你有多个流,并希望将它们合并成一个流,可以使用 flatMap
:
Stream<Integer> stream1 = Stream.of(1, 2, 3);
Stream<Integer> stream2 = Stream.of(4, 5, 6);
Stream<Integer> mergedStream = Stream.of(stream1, stream2)
.flatMap(Function.identity());
List<Integer> mergedList = mergedStream.collect(Collectors.toList());
在这个示例中,flatMap
用于将多个流合并成一个流,最终生成了包含所有元素的单一流。
总之,flatMap
是一个非常强大的操作,可以用于处理各种情况下的流合并和数据转换。它允许你以更灵活的方式操作和转换流中的元素。
4、sorted() / sorted(Comparator<T> comparator):对流中的元素进行排序。
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
List<Integer> sortedNumbers = numbers.stream()
.sorted() // 默认升序排序
.collect(Collectors.toList());
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> sortedNames = names.stream()
.sorted(Comparator.reverseOrder()) // 自定义降序排序
.collect(Collectors.toList());
5、distinct():去除流中的重复元素。
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
6、limit(long maxSize):限制流中元素的数量。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> limitedNumbers = numbers.stream()
.limit(3) // 限制为前3个元素
.collect(Collectors.toList());
7、skip(long n):跳过流中的前N个元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> skippedNumbers = numbers.stream()
.skip(2) // 跳过前2个元素
.collect(Collectors.toList());
3、Stream流中的终结操作
Stream流中的终结操作是用于触发流的遍历或计算的操作,一旦执行终结操作,流就被消耗,不可再次使用。终结操作通常会返回一个非流的结果,如集合、数组、单一值等。以下是一些常见的终结操作:
1、forEach(Consumer<T> action):对流中的每个元素执行指定的操作。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.forEach(System.out::println); // 打印每个名字
2.collect(Collector<T, A, R> collector):将流中的元素收集到一个集合或其他数据结构中
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> collectedNames = names.stream()
.collect(Collectors.toList()); // 将名字收集到List中
3、toArray():将流中的元素转换为数组
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String[] nameArray = names.stream()
.toArray(String[]::new); // 将名字转换为数组
4、reduce(BinaryOperator<T> accumulator):通过指定的二元操作符逐个合并流中的元素,返回最终结果。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b); // 计算总和
reduce(0, (a, b) -> a + b)
:在流上调用 reduce
方法。reduce
方法接受两个参数:
- 第一个参数
0
是初始值(也叫累加器的初始值),表示计算的起始点。 - 第二个参数
(a, b) -> a + b
是一个二元操作符,它定义了如何合并流中的元素。在这里,这个操作符表示将两个元素相加。
reduce
操作首先将初始值 0
赋给累加器 a
,然后遍历流中的元素,将每个元素与累加器 a
执行二元操作符 (a, b) -> a + b
,即相加,然后将结果再次赋给累加器 a
。这个过程会持续遍历流中的元素,直到处理完所有元素。
因此,这行代码的结果是计算了 numbers
列表中所有整数的总和。在这个示例中,总和是 1 + 2 + 3 + 4 + 5 = 15
。
5、count():统计流中元素的数量
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
long count = names.stream()
.count(); // 计算名字的数量
6、anyMatch(Predicate<T> predicate) / allMatch(Predicate<T> predicate) / noneMatch(Predicate<T> predicate):检查流中的元素是否满足指定条件。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean anyEven = numbers.stream()
.anyMatch(n -> n % 2 == 0); // 是否存在偶数
boolean allEven = numbers.stream()
.allMatch(n -> n % 2 == 0); // 是否都是偶数
boolean noneOdd = numbers.stream()
.noneMatch(n -> n % 2 != 0); // 是否没有奇数
7、findFirst()
和 findAny()
是用于查找流中元素的操作,但在一些情况下,它们的行为略有不同:
-
findFirst():这个操作会在流中找到第一个符合条件的元素并返回。在串行流中,它会返回第一个元素,而在并行流中,它会返回最早被处理到的元素。通常在需要获取流中的第一个元素时使用。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> first = names.stream()
.findFirst(); // 查找第一个名字,可能返回 "Alice"
2、findAny():这个操作会在流中找到任意一个符合条件的元素并返回。在串行流中,通常与 findFirst()
的行为相同,都是返回第一个元素。但在并行流中,findAny()
会返回最早被处理到的符合条件的元素,它的行为在多线程情况下更适用。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> any = names.parallelStream()
.findAny(); // 查找任意一个名字,可能返回 "Alice" 或 "Bob" 或 "Charlie"
总之,findFirst()
和 findAny()
用于在流中查找元素。通常在串行流中,它们的行为相似,都会返回第一个符合条件的元素。在并行流中,findAny()
的行为更适合多线程处理,因为它不会受到元素处理顺序的影响,只要找到一个符合条件的元素就会立即返回。
4、注意事项
Java中的Stream流的关键特性:
-
惰性求值(Lazy Evaluation):这是Stream流的一个重要特性。它意味着在没有终结操作的情况下,中间操作不会立即执行。相反,它们会等待直到终结操作被调用才开始执行。这个特性允许你在处理大数据集合时进行优化,只计算必要的部分,而不会浪费计算资源。
例如,你可以通过链式操作来过滤、映射、排序等,而不必一次性对整个数据集合执行所有操作。这可以节省时间和内存。
-
流是一次性的(One-Shot):一旦对一个流对象执行了终结操作,这个流就不能再被使用。这是因为终结操作会消耗流中的元素,流会被消耗掉,无法再次遍历或操作。
因此,如果你需要对同一组数据执行多个操作,应该创建多个流对象,而不是尝试在同一个流上多次调用终结操作。
-
不会影响原数据(Non-Destructive):Stream流的中间操作通常不会对原始数据产生影响。当你在流中进行过滤、映射、排序等操作时,原始数据集合的元素不会被修改。这是因为Stream流的操作是不可变的,它们创建了一个新的流对象,而不是改变了原始数据。
这个特性使得你可以安全地进行多个操作而不必担心原始数据的改变,这对于数据处理的可维护性和可预测性非常重要。
这些特性使得Stream流成为Java中强大的数据处理工具,特别适用于处理集合数据和大规模数据集。理解这些特性可以帮助你更好地使用Stream流来编写高效、可维护的代码。
5、Optional
Optional
是 Java 8 引入的一个类,用于更好地处理可能为空(null)的值,以减少空指针异常的风险。它提供了一种包装可能为空的值的方式,并提供了一系列方法来处理这些值。以下是 Optional
的一些常见用法:
-
创建 Optional 对象:
使用Optional.of(T value)
:创建一个包含非空值的Optional
对象。如果传入的值为null
,将抛出NullPointerException
。
Optional<String> optional = Optional.of("Hello");
使用 Optional.ofNullable(T value)
:创建一个 Optional
对象,如果传入的值为 null
,则创建一个空的 Optional
对象
Optional<String> optional = Optional.ofNullable(null); // 创建一个空的 Optional
使用 Optional.empty()
:创建一个空的 Optional
对象
Optional<String> optional = Optional.empty(); // 创建一个空的 Optional
检查是否包含值:
-
使用
isPresent()
:检查Optional
是否包含非空值。
Optional<String> optional = Optional.of("Hello");
if (optional.isPresent()) {
// 包含值
}
6、综合例子
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class StreamExample {
public static void main(String[] args) {
// 创建学生列表
List<Student> students = Arrays.asList(
new Student("Alice", 22),
new Student("Bob", 18),
new Student("Charlie", 25),
new Student("David", 19),
new Student("Eve", 30)
);
// 使用Stream流处理学生数据
List<Student> result = students.stream()
// 过滤年龄大于等于20的学生
.filter(student -> student.getAge() >= 20)
// 按照年龄降序排序
.sorted(Comparator.comparing(Student::getAge).reversed())
// 收集结果到List
.collect(Collectors.toList());
// 打印结果
System.out.println("年龄在20岁以上的学生按降序排序:");
result.forEach(System.out::println);
}
}
在这个示例中,首先创建了一个Student
类来表示学生对象,包含姓名和年龄属性。然后,我们创建了一个包含学生数据的列表students
。
接下来,我们使用Stream流来处理学生数据:
- 使用
stream()
方法将列表转换为流。 - 使用
filter
中间操作过滤出年龄大于等于20的学生。 - 使用
sorted
中间操作按照年龄降序排序。 - 最后,使用
collect
终结操作将结果收集到一个新的List中。
最终,我们打印出年龄在20岁以上的学生按照降序排序的结果。这个示例演示了如何结合使用Stream流的中间操作和终结操作来处理和转换数据。
当结合 Optional
与 Stream 流一起使用时,通常用于处理可能为空的元素。下面是一个示例,演示如何使用 Optional
和 Stream 流来处理包含学生数据的列表,找出年龄在 20 岁以上的学生,并按照年龄降序排序
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class OptionalStreamExample {
public static void main(String[] args) {
// 创建包含学生数据的列表,可能包含 null 元素
List<Optional<Student>> students = Arrays.asList(
Optional.of(new Student("Alice", 22)),
Optional.empty(),
Optional.of(new Student("Bob", 18)),
Optional.of(new Student("Charlie", 25)),
Optional.ofNullable(null),
Optional.of(new Student("David", 19)),
Optional.of(new Student("Eve", 30))
);
// 使用 Stream 流处理学生数据
List<Student> result = students.stream()
// 过滤出包含学生对象且年龄大于等于 20 的元素
.filter(Optional::isPresent)
.map(Optional::get)
.filter(student -> student.getAge() >= 20)
// 按照年龄降序排序
.sorted(Comparator.comparing(Student::getAge).reversed())
// 收集结果到 List
.collect(Collectors.toList());
// 打印结果
System.out.println("年龄在 20 岁以上的学生按降序排序:");
result.forEach(System.out::println);
}
}
在这个示例中,我们创建了一个包含学生数据的列表 students
,这些数据可能包含 null
或空的 Optional
对象。然后,我们使用 Stream 流来处理学生数据:
- 我们首先通过
filter
过滤掉空的Optional
元素,然后通过map
将Optional
对象转换为学生对象。 - 接下来,我们再次使用
filter
过滤出年龄大于等于 20 岁的学生。 - 使用
sorted
按照年龄降序排序。 - 最后,使用
collect
终结操作将结果收集到一个新的列表中。
通过这种方式,我们可以安全地处理包含 null
或空的 Optional
对象,并找出符合条件的学生。这个示例演示了如何将 Optional
与 Stream 流结合使用来处理数据。