什么是数值流,什么是对象流?


在上篇博客中,Stream初步认识中我们已经知道了什么是流。
java8中将流又可以细分为:数值流和对象流,而平时我们用的最多的就是对象流。
接下里我们就详细说说什么是数值流,什么又是对象流。


直接上代码更容易理解:

  1. @Test
  2. public void test(){
  3. /*
  4. *1.1 数值流就是流中的元素都是基本数据类型(int),对象流就是流中元素为基本数据类型的包装数据类型(Integer)
  5. * 解析:
  6. * * 数组的数据类型为包装类 :Integer 返回为包装类的流
  7. * * 数组的数据类型为基本数据类型 :int 返回为流对象
  8. */
  9. //1.1.1 返回对象流
  10. Integer[] arr = {1,2,3,4,5,6,7,8,9};
  11. Stream<Integer> stream = Arrays.stream(arr);
  12. //1.1.2 返数值流
  13. int[] arrInts = {1,2,3,4,5,6,7,8,9};
  14. IntStream stream1 = Arrays.stream(arrInts);
  15. }

从上面的测试示例上,很直观的就能明白,数值流就是:流中的元素都是基本数据类型,对象流就是流中的元素为基本数据类型的包装类

公共集合


下面所有测试用例中的集合,都是公共集合。

  1. List<Person> personList = new ArrayList<Person>() {
  2. {
  3. add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 2000, 18));
  4. add(new Person("Tamsen", "Brittany", "Java programmer", "female", 2371, 55));
  5. add(new Person("Floyd", "Donny", "Java programmer", "male", 3322, 25));
  6. add(new Person("Sindy", "Jonie", "Java programmer", "female", 35020, 15));
  7. add(new Person("Vere", "Hervey", "Java programmer", "male", 2272, 25));
  8. add(new Person("Maude", "Jaimie", "Java programmer", "female", 2057, 87));
  9. add(new Person("Shawn", "Randall", "Java programmer", "male", 3120, 99));
  10. add(new Person("Jayden", "Corrina", "Java programmer", "female", 345, 25));
  11. add(new Person("Palmer", "Dene", "Java programmer", "male", 3375, 14));
  12. add(new Person("Addison", "Pam", "Java programmer", "female", 3426, 20));
  13. }
  14. };

为什么使用数值流


在文章开头,我就提到我们平时使用最多的就是对象流,那为什么JAVA8还要提供数值流呢?
首先抛出一个示例研究分析一下:

  1. //2.1.1 计算集合中的所有人的薪资总数
  2. int reduce = personList.stream()
  3. // private int salary,age;map()方法通过方法的引用获取的每一个人的薪资都是int类型的
  4. .map(Person::getSalary)
  5. .reduce(0, Integer::sum);

这是一个简单的对集合流的操作:计算所有人薪资总和
我们将这一段代码分为两部分进行分析:
数值流与对象流的转化及其方法使用 - 图1第一部分:map()取值操作,将所有对象的薪资取出
数值流与对象流的转化及其方法使用 - 图2首先是取值操作:

  1. @Data
  2. @Builder(toBuilder = true)
  3. @AllArgsConstructor
  4. @NoArgsConstructor
  5. public class Person {
  6. private String firstName, lastName, job, gender;
  7. private int salary,age;
  8. }
  9. 通过插件获取的属性值是`int`类型的。

数值流与对象流的转化及其方法使用 - 图3我们在看map的源码:

  1. <R> Stream<R> map(Function<? super T, ? extends R> mapper);

根据map操作的源码我们可以看到,map操作后返回的是Integer类型的对象流Stream<R>
那么这里就反应出一个问题:我们传入的是int基本数据类型,而map操作之后返回的却是Integer包装类型的对象流。也就是说这其中暗含了一步封箱操作

数值流与对象流的转化及其方法使用 - 图4第二部分:reduce()规约操作,通过引用sum方法进行求和
数值流与对象流的转化及其方法使用 - 图5首先我们sum求和方法的源码:

  1. public static int sum(int a, int b) {
  2. return a + b;
  3. }

根据sum的源码我们发现Integer::sum 这个方法引用,它引用的方法的参数和返回值都是int。
因此规约求和后的返回值确实是int。
这就反应出第二个问题:map操作后,结果为Integer对象流Stream ,然后要进行reduce里的求和操作就需要先进行拆箱操作(integer->int),返回int类型。

下面我们用数值流实现这个需求:

  1. //2.1.2 优化上面的操作(主要是减少封箱拆箱操作)
  2. int sum = personList.stream()
  3. .mapToInt(Person::getSalary) // IntStream mapToInt(ToIntFunction<? super T> mapper);
  4. .sum(); // int sum();

然后将两者进行对比,使用数值流操作减少了中间的反复拆箱和封箱操作,而这些操作都是需要耗费内存资源的,同时对象流stream 也没有数值流这种简单直接的求和方法 sum()可调用(当然这只是其中一个方法)。这就是为什么java8 专门提供了数值流给我们使用。

小结
数值流与对象流的转化及其方法使用 - 图6 使用对象流Stream操作的弊端,就是会频繁的进行封箱,拆箱,严重浪费了内存资源,
数值流与对象流的转化及其方法使用 - 图7
使用数值流可以减少这一行为,提高内存利用率。
数值流与对象流的转化及其方法使用 - 图8* 另外操作对象流也没有数值流这样简单直接,也这是java8提供数值流的原因

Numeric streams 数值流的使用


数值流与对象流的转化及其方法使用 - 图9 java8 引入了三个基本数据类型流接口:IntStream、DoubleStream和LongStream每个接口分别对应流中的元素为:int 、long和double,从而避免减少出现暗含的封箱、拆箱成本。同时每个接口都带来了进行常用数值reduce操作的新方法,比如对数值流求和的sum(如果流是空的,sum返回值为0),还有max、min、average等方法。

1. 对象流转数值流


将对象流转换为数值流的常用方法是mapToInt、mapToDouble和mapToLong

  1. //3.1 对象流转数值流 将对象流转换为数值流的常用方法是mapToInt、mapToDouble和mapToLong为例:
  2. Integer[] arr = {1,2,3,4,5,6,7,8,9};
  3. Double[] arrs = {1.1,2.5,3.6,4.4,5.5,6.4,7.1,8.1,9.7};
  4. //3.1.1 mapToInt()方法
  5. IntStream intStream = Arrays.stream(arr).mapToInt(e -> e.intValue());
  6. //3.1.2 mapToDouble()方法
  7. DoubleStream doubleStream = Arrays.stream(arrs).mapToDouble(e -> e.doubleValue());
  8. //3.1.3 mapToLong()方法
  9. LongStream longStream = Arrays.stream(arr).mapToLong(e -> e.longValue());

2. 数值流转为对象流


为什么:为数值流里面的方法受众面可能比较窄,方法拓展性也弱

  1. /*3.2 数值流转为对象流 boxed()方法
  2. * 为什么:为数值流里面的方法受众面可能比较窄,方法拓展性也弱
  3. */
  4. Integer[] arrBoxed = {1,2,3,4,5,6,7,8,9};
  5. Stream<Integer> objStream = Arrays.stream(arrBoxed);//返回对象流
  6. IntStream intStream1 = objStream.mapToInt(e -> e.intValue());//对象流---->数值流
  7. Stream<Integer> boxed = intStream1.boxed();//数值流---->对象流
  8. //优化
  9. Arrays.stream(arrBoxed).mapToInt(e -> e.intValue()).boxed();

3. 默认值OptionalInt


Optional这个类,这是一个可以表示值存在或不存在的java容器。针对三种数值流,也分别有一个Optional数值类型版本:OptionalInt、OptionalDouble和OptionalLongOptional详解

这个的作用体现在哪里呢?看下面这个例子,它的流是空的,但是我们对这个流进行reduce操作时,我们给了它一个初始参数 0,并希望进一步求得到这个流中的最大值,运行程序结果为:0。那么我们这个流中的最大值就是0了吗?这个结果是我们想要的吗?显然不是,事实是这个流不存在最大值,因为他是空的,输出的0不过是我们reduce的一个初始参数0。

  1. Integer reduce1 = Arrays.stream(arrNull).reduce(0, Integer::max);
  2. //打印结果为:0 (需要注意reduce的初始化参数为0)

当你需要考虑到流没有最大值的情况时(当然这只是一种需求情况而已),你就可以显式处理OptionalInt,去定义一个默认值,避免出现上面那个例子的混淆结果:

  1. //3.3 默认值OptionalInt
  2. Integer[] arrNull = {};
  3. Stream<Integer> stream = Arrays.stream(arrNull);//对象流
  4. OptionalInt max1 = stream.mapToInt(i -> i.intValue()).max();//转为数值流求最大值
  5. int reduce = max1.orElse(10000000);//如果max方法返回值为空,则返回100000000
  6. System.out.println(reduce);
  7. int i1 = Arrays.stream(arrNull).mapToInt(i -> i.intValue()).max().orElse(10000000);
  8. System.out.printf("优化:"+i1);

4. 数值范围


Java8中还引入了两个可以用于IntStream和LongStream的静态方法帮助生成这种范围:range和rangeClosed

  1. //3.4 数值范围 Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed
  2. System.out.println("rangeClosed则包含结束值:");
  3. //rangeClosed() 第一个参数接受起始值,第二个参数接受结束值
  4. IntStream.rangeClosed(1, 100).filter(s1 -> s1 % 2 == 0).forEach(e-> System.out.printf("%s%s"," ",e));
  5. System.out.println("range是不包含结束值的:");
  6. //range()第一个参数接受起始值,第二个参数接受结束值
  7. IntStream.range(1, 100).filter(s1 -> s1 % 2 == 0).forEach(e-> System.out.printf("%s%s"," ",e));

结束语


辛苦搬砖的打工人,明天休息,面临着找地方搬家,太难了。
继续保持搬砖精神,


推荐博客:

  1. 数值流与对象流的转化及其方法使用:https://blog.csdn.net/aigoV/article/details/102897762