Java中的十个非常必要掌握的流操作
在此文中我为读者编写了有关Java流操作的详细示例
1.map:
用于将流中的每个元素通过一个函数以产生新的流。
示例:使用map操作将每个宠物名称转换为大写,然后将生成的大写名称放到一个新列表中。
List<String> pets = List.of("Hamster", "Cat", "Dog");
List<String> upperCaseNames = pets.stream()
.map(String::toUpperCase)
.toList();
assert List.of("HAMSTER", "CAT", "DOG").equals(upperCaseNames);
2.filter:
此操作用于在一个流中执行一次过滤条件以返回符合条件的元素到新的流。
示例:使用filter操作仅保留偶数,然后将这些偶数放到一个新列表中。
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(number -> number % 2 == 0)
.toList();
assert List.of(2, 4, 6, 8, 10).equals(evenNumbers);
3.collect:
此操作将流的元素转移到某种数据结构中,例如列表、集合或map。
示例:通过使用collect方法和Collectors.toSet,将流的宠物名称转换为一个set,利用set去重特性确保名称唯一。
List<String> fruits = List.of("apple", "peach", "banana", "cherry", "banana", "peach");
Set<String> fruitSet = fruits.stream()
.collect(Collectors.toSet());
assert fruitSet.size() == 4;
4.flatMap:
此操作将流的流(嵌套结构)展平为单个流,从而简化复杂的数据结构。
示例:通过使用flatMap操作将流的流转换为单个流,从而简化嵌套结构。收集后的流包含所有形状。
List<List<String>> shapes = List.of(
List.of("triangle", "rectangle", "square"), // sharp forms
List.of("circle", "ellipse", "cylinder") // rounded forms
);
List<String> flattenedShapes = shapes.stream()
.flatMap(Collection::stream)
.toList();
assert flattenedShapes.size() == 6;
assert List.of("triangle", "rectangle", "square", "circle", "ellipse", "cylinder").equals(flattenedShapes);
5.reduce:
此操作将流中的元素组合成一个单一的结果,例如求和、连接字符串或计算最大值/最小值。
示例:使用reduce操作计算数字列表的总和。初始值设置为0,使用Integer::sum方法引用作为二元操作来执行求和。结果,存储在sum变量中,表示数字列表中所有数字的总和。
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
Integer sum = numbers.stream()
.reduce(0, Integer::sum);
assert sum == 15;
6.forEach:
此操作可以遍历流中的每个元素并执行某种操作。
示例:使用forEach方法对列表中每个数字乘以2,结果打印到控制台。
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.forEach(num -> System.out.println(num * 2));
7.distinct:
此操作删除流中的重复元素,确保唯一性。
示例:通过distinct操作过滤掉重复的元素,返回的不重复数字到新列表中。
List<Integer> numbers = List.of(1, 2, 3, 4, 4, 4, 5);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.toList();
assert List.of(1, 2, 3, 4, 5).equals(distinctNumbers);
8.sorted:
此操作对流中的元素进行排序。
示例:使用sorted操作对数字列表进行升序排序。
List<Integer> numbers = List.of(3, 1, 6, 8, 2, 4, 5, 9, 7);
List<Integer> sorted = numbers.stream()
.sorted()
.toList();
assert List.of(1, 2, 3, 4, 5, 6, 7, 8, 9).equals(sorted);
9.skip和limit:
这两个操作允许跳过流的开头元素并限制要处理的元素数量。
示例:使用skip和limit操作跳过前两个元素,然后从三个元素开始。
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> skipped = numbers.stream()
.skip(2)
.toList();
assert List.of(3, 4, 5, 6, 7, 8, 9, 10).equals(skipped);
List<Integer> limited = numbers.stream()
.limit(3)
.toList();
assert List.of(1, 2, 3).equals(limited);
10.anyMatch、noneMatch和allMatch:
这些操作允许根据给定的条件检查流中的元素,并返回一个布尔值。
示例:使用anyMatch、noneMatch和allMatch操作检查流中的元素是否满足特定条件。将流中的元素与给定的lambda表达式进行比较。
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
assert Boolean.TRUE.equals( // 是否有任一元素等于5?
numbers.stream()
.anyMatch(num -> num == 5)
);
assert Boolean.FALSE.equals( // 是否有任一元素等于15?
numbers.stream()
.anyMatch(num -> num == 15)
);
assert Boolean.TRUE.equals( // 没有元素等于15?
numbers.stream()
.noneMatch(num -> num == 15)
);
assert Boolean.FALSE.equals( // 没有元素等于3?
numbers.stream()
.noneMatch(num -> num == 3)
);
assert Boolean.TRUE.equals( // 所有元素都大于0?
numbers.stream()
.allMatch(num -> num > 0)
);
assert Boolean.FALSE.equals( // 所有元素都偶数?
numbers.stream()
.allMatch(num -> num % 2 == 0)
);
通过结合这些基本操作,可以解决各种问题。在实际应用中可以尝试使用map、filter、reduce等操作组合解决问题,以便更有效地处理数据。
常用示例:
使用流操作来处(Employee类)理员工数据。以下是一个示例,展示了如何使用map、filter和reduce操作处理员工表中的数据。
record Employee(String name, int age, int salary, Gender gender) {
}
enum Gender {
MALE, FEMALE
}
public class Advanced {
enum Gender {
MALE, FEMALE
}
record Employee(String name, int age, int salary, Gender gender) {
}
public static void main(String[] args) {
Employee employee1 = new Employee("张三", 20, 200, Gender.MALE);
Employee employee2 = new Employee("李四", 28, 200, Gender.FEMALE);
Employee employee3 = new Employee("王五", 38, 2750, Gender.MALE);
Employee employee4 = new Employee("赵六", 35, 350, Gender.FEMALE);
Employee employee5 = new Employee("周七", 40, 3100, Gender.MALE);
List<Employee> employees = List.of(employee1, employee2, employee3, employee4, employee5);
// ...
}
}
1:计算男员工的总工资
double summed = employees
.stream()
.filter(employee -> employee.gender.equals(Gender.MALE))
.mapToDouble(Employee::salary)
.sum();
assert summed == 2750 + 2000 + 3100;
2:是否有一个女员工名为“赵六”出生在198年?
boolean existsFemaleEmployeeWithName = employees
.stream()
.filter(employee -> employee.gender.equals(Gender.FEMALE) && employee.name.equals("赵六"))
.anyMatch(employee -> employee.name.equals("赵六"));
assert existsFemaleEmployeeWithName;
3:计算所有员工的平均工资?
double averageSalary = employees
.stream()
.mapToDouble(Employee::salary)
.average()
.orElse(0);
assert averageSalary == 2000;
4:按照工资从高到低排列所有员工?
List<Employee> top3HighestSalaries = employees
.stream()
.sorted(Comparator.reverseOrder())
.limit(3)
.toList();
assert List.of(employee5, employee3, employee1).equals(top3HighestSalaries);
5:计算每个性别的员工数量?
Map<Gender, Integer> genderToTotalSalaryMap = employees
.stream()
.collect(Collectors.groupingBy(Employee::gender, Collectors.summingInt(Employee::salary)));
assert genderToTotalSalaryMap.get(Gender.MALE) == 2750 + 2000 + 3100;
assert genderToTotalSalaryMap.get(Gender.FEMALE) == 2000 + 350;
这些例子可能并不能完全反映出日常工作中遇到的情况,但几乎相似。通过掌握如何使用这些操作函数,你可以将复杂问题处理得游刃有余。
并行流
流可以使用多个线程同时进行工作。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.parallelStream() // 并行处理
.mapToInt(n -> n) // 转换为IntStream
.sum(); // 添加所有数字
在大量数据处理中使用并行流可以显著提高性能。
不可变的数据
流一旦建立,就不能更改。
List<String> names = Arrays.asList("Zhanjiang", "ZhongGuo", "ZhouLai");
List<String> upperCaseNames = names.stream() // 制作一个流
.map(String::toUpperCase) // 使所有名称大写
.collect(Collectors.toList()); // 收集到新列表中
每个对流的操作都会产生一个新的流。
Optional
流通常与Optional一起使用,以处理可能为NULL的值。
Optional<String> firstFruit = fruits.stream() // 制作一个流
.findFirst(); // 查找第一个水果
findFirst操作返回流中第一个元素的Optional,或者如果流为空,则返回一个空的Optional。这有助于优雅地处理空值。