Java函数式编程【二】【Stream的装饰】【中间操作】【map映射器】【摊平映射器flatMap】

一、Java的Stream流式编程中的中间操作

Java的Stream流式编程中,中间操作是对数据流进行处理的一种方式,这些操作通常返回流对象本身,以便可以链接更多的操作。以下是一些常见的中间操作:

  • filter(Predicate predicate) - 用于通过设定的条件过滤出元素。

  • sorted() - 对元素进行排序。

  • distinct() - 去除重复的元素。

  • limit(long maxSize) - 获取指定数量的流元素。

  • skip(long n) - 跳过操作,跳过某些元素。

  • peek() - 查看操作。允许你在不影响流的主要处理逻辑的情况下,查看或使用流中的每个元素。这个方法可以用来进行一些调试或日志记录等操作。下面是一个示例:

peek例程

cpp 复制代码
List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape");
fruits.stream()
    .filter(f -> f.length() > 5)
    .peek(System.out::println)
    .collect(Collectors.toList());

中间操作的示例代码:

cpp 复制代码
List<String> items = Arrays.asList("apple", "banana", "orange", "kiwi");
 
// Filtering
List<String> filteredItems = items.stream()
    .filter(item -> item.startsWith("a"))
    .collect(Collectors.toList());

// Sorting
List<String> sortedItems = items.stream()
    .sorted()
    .collect(Collectors.toList());
 
// Distinct
List<String> distinctItems = items.stream()
    .distinct()
    .collect(Collectors.toList());
 
// Limiting
List<String> limitedItems = items.stream()
    .limit(2)
    .collect(Collectors.toList());
 
// Skipping
List<String> skippedItems = items.stream()
    .skip(1)
    .collect(Collectors.toList());

函数式编程,在流管道中可以包含0个或n个中间操作,每个中间操作都返回一个新的流。

二、中间操作 映射器map()的用法

映射器map的方法签名(原型):

  • map(Function<? super T, ? extends R> mapper) - 它的输入参数是一个类型为函数接口的映射器,可将流中的元素转换成其他类型的元素。映射器map()是把一个对象流变成另一种对象流。

另外三个与映射器map()类似的映射器,它们则可以把一个对象流转换为基本类型流。

  • mapToInt(ToIntFunction<? super T> mapper) - 将元素映射为值的整型int流。
  • mapToLong(ToLongFunction<? super T> mapper) - 将元素映射为值的长整型long流。
  • mapToDouble(ToDoubleFunction<? super T> mapper) - 将元素映射为值的双精度浮点型double流。

用法一:将一种流映射成另一种流

把英文的小写转换为大写的代码片断:

cpp 复制代码
// Mapping
List<String> items = Arrays.asList("apple", "banana", "orange", "grape");
List<String> mappedItems = items.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());

下面是来看一个完整的示例。下图是这个流管道的详细分解说明图示:

这个示例完整的程序源码:

cpp 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MapTest {
	public static void main(String[] args) {
		List<User> list = Arrays.asList(new User(2001, "Ricky"), new User(2002, "Alex"), new User(2003, "Bob"));
		list.forEach(System.out::println);
		List<String> newList = list.stream().map(User::getName).sorted().limit(2).collect(Collectors.toList());
		newList.forEach(System.out::println);
	}
}

class User {
	private int id;
	private String name;
	public User(int id,String name) {
		this.id = id;
		this.name = name;
	}
	public String getName() {
		return name;
	}
	@Override
	public String toString() {
		return "{User[ID:"+id+" 姓名:"+name+"]}";
	}
}

例程测试效果图:

映射器map示例: 实现功能:整数列表每个元素+3。分别用对象流和IntStream来实现。

使用的两个映射器的原型如下所示:

cpp 复制代码
//本示例使用的map()方法的签名,其入口参数类型是Function函数,如下:
<Integer> Stream<Integer> java.util.stream.Stream.map(Function<? super Integer, ? extends Integer> mapper)
//本示例使用的mapToInt()方法的签名,其入口参数类型是ToIntFunction函数,如下
IntStream java.util.stream.Stream.mapToInt(ToIntFunction<? super Integer> mapper)

这两个流,尽管入口参数的函数类型不一样,但完全可以用同一个Lambda表达式来代替函数接口的实例作为传入参数。

但后续的处理方式有些不同:映射器map()返回的是对象流,可用收集器collect(Collectors.toList())来收集结果。映射器mapToInt()返回的是IntStream(整型流),不能使用用收集器。因为收集器收集的元素必须是对象,IntStream中的元素是基本数据类型,所以收集器不支持。

cpp 复制代码
		List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11);

		//对象流的实现方式
		List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList());
		System.out.println("每个元素+3:" + intListNew);

		//用IntStream来实现
		System.out.println("打印IntStream:");
		IntStream intStream = intList.stream().mapToInt(x -> x + 3);
		intStream.forEach(System.out::println);

测试效果图:

mapToDouble基本数据类型流例程

基本数据类型流,比如DoubleStream(双精度浮点型流)不能使用收集器收集结果,但也有很多的终止操作,比如求最大最小值、求和、求平均值:

cpp 复制代码
public static void main(String[] args) {
    List<Double> doubleList = Arrays.asList(1.0, 22.0, 3.0, 4.0, 32.0);
    double average = doubleList.stream().mapToDouble(Number::doubleValue).average().getAsDouble();
    double sum = doubleList.stream().mapToDouble(Number::doubleValue).sum();
    double max = doubleList.stream().mapToDouble(Number::doubleValue).max().getAsDouble();
    System.out.println("平均值:" + average + ",总和:" + sum + ",最大值:" + max);
}

测试结果: 平均值:12.4,总和:62.0,最大值:32.0

三、中间操作 摊平映射器flatMap()的用法

  • flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) - 将每个元素转换为某种元素类型的流,然后将它们连接成一个流。

摊平映射器flatMap()示例一:

cpp 复制代码
public class StreamTest {
	public static void main(String[] args) {
		List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
		List<String> listNew = list.stream().flatMap(s -> {
			// 将每个元素转换成一个stream
			String[] split = s.split(",");
			return Arrays.stream(split);
		}).collect(Collectors.toList());

		System.out.println("处理前的集合:" + list);
		System.out.println("处理后的集合:" + listNew);
	}
}

在这个例子中,初始时对象流的元素类型是长度为7的字符串:"m,k,l,a"和"1,3,5,7"。[ "m,k,l,a" , "1,3,5,7" ]。通过摊平映射器flatMap()转换后的对象流的元素类型是长度为1的字符串。最终对象流为[ "m" , "k" , "l" , "a" , "1" , "3" , "5" , "7" ]

实际上,上面的示例程序还可以简化,可简化为:

cpp 复制代码
	List<String> listNew = list.stream().flatMap(str->Stream.of(str.split(","))).collect(Collectors.toList());

摊平映射器flatMap()示例二:

cpp 复制代码
package stream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FlatMapTest {
	public static void test1() {
		List<String> items = Arrays.asList("glass", "brick","gold", "silver");
		List<String> flatMappedItems = items.stream().flatMap(item -> Stream.of(item.split(""))).collect(Collectors.toList());
		flatMappedItems.forEach(System.out::println);
	}

	public static void test2() {
		String songs = "Remember me to one who lives there";
		String[] words = songs.split(" ");
		List<String> strList = Arrays.stream(words).map(s -> s.split("e"))
				.flatMap(e -> Arrays.stream(e))
				.collect(Collectors.toList());
		strList.forEach(System.out::println);
	}
	public static void main(String[] args) {
		test1();
		test2();
	}
}

测试结果图:

流的连接:有两种方式。如果是两个流的连接,可使用 Stream.concat() 方法;

如果是三个及三个以上流的连接,就使用 摊平映射器flatMap() 方法。

摊平映射器flatMap还可以用来实现流的连接,请看例程:

cpp 复制代码
	public static void concatStream() {
		String names[][] = { {"Alice", "Alien", "Bob"},
			{"Jack", "John", "Jobs"},{"Maria", "Golf", "Korea"} };
        //两个流的连接
        Stream<String> concat = Stream.concat(Arrays.stream(names[0]), Arrays.stream(names[1]));
        concat.forEach(System.out::println);
        System.out.println("===========");
        //多个流的连接,例子之一
        List<String[]> list = Arrays.asList(names);
        Stream<String> strStream = list.stream().flatMap(e->Arrays.stream(e));
        strStream.forEach(System.out::println);
        
        System.out.println("===========");
        Stream<String> first = Stream.of("Alice", "Alien", "Bob");
        Stream<String> second = Stream.of("Jack", "John", "Jobs");
        Stream<String> third = Stream.of("Maria", "Golf", "Korea");
        //多个流的连接,例子之二
        //Stream<String> stringStream = Stream.of(first, second, third).flatMap(s->s);
        Stream<String> stringStream = Stream.of(first, second, third).flatMap(Function.identity());
        stringStream.forEach(System.out::println);
	}

说明: 多个流的连接,"例子之一"和"例子之二"实现相同功能。另外,示例中"flatMap(Function.identity())"等价于"flatMap(s->s)"。

摊平映射器有很多,例如摊平为基本数据类型流的映射器(可由类型T的对象流转换为基本数据类型的流):

  • IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
  • LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
  • DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);

映射器map和flatMap的区别

map()操作将每个元素转换成一个新元素,并将所有这些新生成的元素收集到一个新的流中。

flatMap()操作将每个元素转换成一个新的流,并将所有这些新生成的流合并成一个单一的流。

flatMap()和map()之间还有一个重要的区别,那就是flatMap()支持处理包含嵌套数据结构的流。

参考文献:

第1篇:lambda表达式会用了么?
第2篇:Java Stream API?
第3篇:Stream的Filter与谓词逻辑
第4篇:Stream管道流Map操作
第5篇:Stream的状态与并行操作
第7篇:像使用SQL一样排序集合
第8篇-函数式接口
第9篇-元素的匹配与查找
第10篇-集合元素归约
第11篇-Stream API终端操作

Java Stream函数式编程第三篇:管道流结果处理

相关推荐
小海编码日记9 分钟前
Java八股-JVM & GC
java
全职计算机毕业设计15 分钟前
基于Java Web的校园失物招领平台设计与实现
java·开发语言·前端
东阳马生架构21 分钟前
商品中心—1.B端建品和C端缓存的技术文档
java
Chan1624 分钟前
【 SpringCloud | 微服务 MQ基础 】
java·spring·spring cloud·微服务·云原生·rabbitmq
LucianaiB27 分钟前
如何做好一份优秀的技术文档:专业指南与最佳实践
android·java·数据库
面朝大海,春不暖,花不开1 小时前
自定义Spring Boot Starter的全面指南
java·spring boot·后端
得过且过的勇者y1 小时前
Java安全点safepoint
java
夜晚回家1 小时前
「Java基本语法」代码格式与注释规范
java·开发语言
斯普信云原生组2 小时前
Docker构建自定义的镜像
java·spring cloud·docker
wangjinjin1802 小时前
使用 IntelliJ IDEA 安装通义灵码(TONGYI Lingma)插件,进行后端 Java Spring Boot 项目的用户用例生成及常见问题处理
java·spring boot·intellij-idea