Java函数式编程【一】【Stream API概述】【Stream的创建】

一、Stream(流)API概述

Java 8引入的Stream API是一种处理集合数据的高级抽象,它允许以声明式的方式对集合进行操作,使得代码更加简洁和易读。
Stream(流)是特定类型的数据元素形成的一个队列并支持聚合操作。 聚合操作类似数据库查询数据的SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等,是对集合运算和表达的高阶抽象。Java中的Stream并不会存储元素,而是按需计算的。

Stream(流)与操作相关的两个术语:

  • 惰性求值
  • 主动求值

在Java Stream(流)中,惰性求值和主动求值的概念非常重要。惰性求值意味着在Stream(流)被完全消费之前,中间操作不会进行实际的计算。而主动求值则是在终止操作被调用时,整个Stream(流)的处理才会开始执行,并得到操作结果。
如何判断一个Stream(流)操作是惰性求值还是主动求值呢?
如果一个Stream(流)的操作,其返回值是一个Stream(流),那么这就是一个惰性求值操作。否则,若一个Stream(流)的操作,其返回值不是一个Stream(流),而是最终结果,那么这是一个主动求值操作。

Stream API提供了两个关键抽象:Stream(流) 数据元素有限或无限的序列;Stream pipeline(流管道) 则表示这些数据元素的多级计算。
Stream不是数据结构,它不会存储数据,而是按需计算的,是对数据源(如集合、数组和文件等)进行一系列的操作,并返回一个新的Stream或一个最终结果。

Stream(流)的三种操作:

  • 创建流(Stream): 流(Stream)可由数据源创建。数据源各种各样,例如:集合、数组、文件、I/O channel、迭代器()、产生器(generator)、字符串、正则表达式和数据库的结果集等等;
  • 装饰流(Stream): 通常,装饰流(Stream)又称为流的中间操作(intermediate operation)。一个流(Stream)的算式中可以同时包含n多个中间操作,每个中间操作返回一个新的流(Stream)。这样多个装饰流可以串联成一个管道,如同流水线。所有的装饰流都是惰性求值操作。
  • 终结流(Stream): 终结流(Stream)又称为流的终端操作(terminal operation)。终端操作都是及早求值操作。

在这里插入图片描述

内部迭代与外部迭代

以集合框架中的列表为例,下面定义一个行星的列表List starsLst:

		List<String> starsLst = Arrays.asList("Mercury","Venus","Earth","Mars",
				"Jupiter","Saturn","Uranus","Neptune");

在面向对象的编程时,Java程序员在使用集合框架中的集合类时,一个通用的模式时在集合上进行迭代,在迭代过程中处理每一个元素并返回处理结果。通常代码时这样的:

		/***最老式的外部迭代写法***/
		List<String> starsLst = Arrays.asList("Mercury","Venus","Earth","Mars",
				"Jupiter","Saturn","Uranus","Neptune");
		Iterator<String> iterator=starsLst.iterator();
		String star ;
		while(iterator.hasNext()){
			star = iterator.next();
			if (star.length()<6) System.out.println(star);
		}

或者,也可以这样写:

		/***forEach的外部迭代写法***/
		List<String> starsLst = Arrays.asList("Mercury","Venus","Earth","Mars",
				"Jupiter","Saturn","Uranus","Neptune");
		for (String star : starsLst) {
			if (star.length()<6) System.out.println(star);
		}

上面这两种写法,都是外部迭代,问题在于首先,很难抽象出后续提到的复杂操作,另外从本质上来讲只是一种串行化操作。总体来看使用for循环会将行为和方法混为一谈。

Stream(流)的内部迭代,与调用iterator()作用相似,该方法不是返回一个控制迭代的Iterator对象,而是返回内部迭代中的相对应接口:Stream。
不需要多次循环,一次迭代就可以得到全部结果,例如 :下面的找出长度小于6的行星名称并打印出来。

	/***Stream(流)的内部迭代写法***/
	List<String> starsLst = Arrays.asList("Mercury","Venus","Earth","Mars",
			"Jupiter","Saturn","Uranus","Neptune");
	starsLst.stream().filter(e -> e.length()<6).forEach(System.out::println);

函数式编程
Stream API引入了一种流式的函数式编程方式,Stream(流)可以把集合元素组织为一种有序的元素流,元素流在流管道中传输,并在流管道节点上经过中间操作(intermediate operation)的处理(例如:筛选、排序、映射和聚合等),最后由终端操作(terminal operation)返回最终的结果。

函数式编程的程序流程示意图:
在这里插入图片描述

流管道示意图
在这里插入图片描述

函数式编程的所需的知识结构
使用Java 的Stream API进行函数式编程需要掌握两种基础知识:

  • lambda表达式(包括方法引用)的相关知识;
  • 函数接口相关知识

这两种基础知识可参见:
Java函数式编程与【函数接口】【Lambda 表达式】综述
Java函数式编程基础之【Lambda表达式】语法与示例

Stream(流)由数据源创建,Stream(流)的操作不会改变数据源。
两种Stream(流) 在 Java 8 中, 集合框架中的接口通常都有两个方法来生成流:

  • 串行顺序流:方法stream() 为集合创建串行流。串行流执行单线程的串行按顺序处理。
  • 并行流:方法parallelStream() 为集合创建并行流。并行流执行多线程的并行处理。

创建串行流和并行流的示例:

package stream;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class SerialStreamParallelStream {
	public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve",null);
        List<String> newNames = names.stream().filter(e -> Objects.nonNull(e))
        		.collect(Collectors.toList());
        long count = names.parallelStream().filter(e -> Objects.nonNull(e)).count();
        System.out.println("人数:" + count);
        newNames.forEach(System.out::println);
	}
}

stream()和parallelStream()的的区别和使用场景

  • 方法stream() 为集合创建顺序流,由主线程按顺序对流执行操作;
  • 方法parallelStream() 为集合创建并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。

例如,筛选集合中的奇数,两者的处理有些不同。请看源代码:

    public static void filterEvenInt() {
    	/***测试是否是偶数***/
    	Predicate<Integer> evenPredicate = num -> num % 2 == 0;
    	List<Integer> list = Arrays.asList(1, 4, 5, 6, 3, 7, 2, 10, 5, 8);
    	System.out.println("顺序流过滤奇数:");
    	list.stream().filter(evenPredicate.negate()).forEach(System.out::println);
    	System.out.println("并行流过滤奇数:");
    	list.parallelStream().filter(evenPredicate.negate()).forEach(System.out::println);
    }

顺序流:list.stream().filter(evenPredicate.negate()).forEach(System.out::println);
并行流:list.parallelStream().filter(evenPredicate.negate()).forEach(System.out::println);
两种流的处理有些不同。示意图(图中并行流只画了2个线程,实际可能有n个线程来处理)如下:
在这里插入图片描述
并行流parallelStream()的遍历有两种:
forEach(System.out::println) 将打乱原始流中元素的排列顺序。
forEachOrdered(System.out::println) 并行处理parallel时将保证按照原始顺序,注意不是排序输出。
示例程序:

        System.out.println("parallel 处理后顺序被打乱");
        IntStream.of(1,2,3,4,5,6,7,8).parallel().forEach(System.out::println);
        System.out.println("parallel 处理后保持原始顺序不变");
        // 在并行遍历时,forEachOrdered将顺序遍历元素
        IntStream.of(1,2,3,4,5,6,7,8).parallel().forEachOrdered(System.out::println);

程序测试结果:
在这里插入图片描述

二、Stream(流)的创建

1,由数组创建Stream(流)

  • 使用Stream.of()方法,将数组转换为Stream(流)。
  • 使用Arrays.stream()方法,将数组转换为Stream(流)。
		/***使用数组创建流***/
	public static void arryStream() {
		//数组创建流,版本一:利用Stream.of()方法创建流
		String arry[] = {"ok","good","luck"};
		Stream<String> arrStream = Stream.of(arry); 
		
		// 数组创建流,版本二:利用Arrays.stream()方法创建流
		Stream<String> stream = Arrays.stream(arry);
	}

2,集合类创建Stream(流)

  • 调用集合类的stream()方法,将集合类对象创建Stream(流)
	/***使用集合创建流***/
	public static void collectionStream() {
        // 由List创建Stream(流)
        List<String> nameList = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
        Stream<String> streamFromList = nameList.stream();

        // 由Set创建Stream(流)
        Set<String> set = new HashSet<>(nameList);
        Stream<String> streamFromSet = set.stream();

        // 由Map创建Stream(流)
        Map<String, Integer> hMap = new HashMap<>();
        int id = 0;
        for (String name : nameList) hMap.put(name, ++id);
        Stream<Map.Entry<String, Integer>> stream = hMap.entrySet().stream();
        stream.forEach(System.out::println);
	}

3,文本文件创建Stream(流)

  • 通过Files.lines()方法将文本文件转换为Stream(流)
	/***使用文件创建Stream<String>***/
	public static void fileStream() {
		String path = "D:/temp/凉州词.txt";
		try {
			Files.lines(Paths.get(path)).forEach(System.out::println);
		} catch (IOException ie) {
			ie.printStackTrace();
		}
	}

4,用字符串(String)创建基本类型流IntStream

	/***使用字符串(String)创建基本类型流IntStream***/
	public static void stringToStream() {
		String str1 = "众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。";
		String str2 = "\uD835\uDD46书藏古今,江通天下。";
		
		IntStream strStream= str1.chars();
		IntStream intStream = str2.codePoints(); //转换成Int流
	}

5,用Stream(流)的静态方法创建流
如静态方法generate()和iterate()等都可创建流。

	public static void crtStream() {
        //利用Stream.of方法创建流
        Stream<String> stream = Stream.of("中国", "IT科技", "崛起");
        stream.forEach(System.out::println);
        System.out.println("==================");
        //利用Stream.iterate方法创建流
        Stream.iterate(10, x -> 2*x + 1)
                .limit(5)
                .collect(Collectors.toList())
                .forEach(System.out::println);
        System.out.println("==================");
        //利用Stream.generate方法创建流
        Stream.generate(Math::random)
                .limit(5)
                .forEach(System.out::println);
        System.out.println("==================");
	}

参考文献:

Java8 Stream流使用
吃透JAVA的Stream流操作,多年实践总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值