什么是数值流,什么是对象流?
在上篇博客中,Stream初步认识中我们已经知道了什么是流。java8中将流又可以细分为:数值流和对象流,而平时我们用的最多的就是对象流。
接下里我们就详细说说什么是数值流,什么又是对象流。
直接上代码更容易理解:
@Test
public void test(){
/*
*1.1 数值流就是流中的元素都是基本数据类型(int),对象流就是流中元素为基本数据类型的包装数据类型(Integer)
* 解析:
* * 数组的数据类型为包装类 :Integer 返回为包装类的流
* * 数组的数据类型为基本数据类型 :int 返回为流对象
*/
//1.1.1 返回对象流
Integer[] arr = {1,2,3,4,5,6,7,8,9};
Stream<Integer> stream = Arrays.stream(arr);
//1.1.2 返数值流
int[] arrInts = {1,2,3,4,5,6,7,8,9};
IntStream stream1 = Arrays.stream(arrInts);
}
从上面的测试示例上,很直观的就能明白,数值流就是:流中的元素都是基本数据类型,对象流就是流中的元素为基本数据类型的包装类
。
公共集合
下面所有测试用例中的集合,都是公共集合。
List<Person> personList = new ArrayList<Person>() {
{
add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 2000, 18));
add(new Person("Tamsen", "Brittany", "Java programmer", "female", 2371, 55));
add(new Person("Floyd", "Donny", "Java programmer", "male", 3322, 25));
add(new Person("Sindy", "Jonie", "Java programmer", "female", 35020, 15));
add(new Person("Vere", "Hervey", "Java programmer", "male", 2272, 25));
add(new Person("Maude", "Jaimie", "Java programmer", "female", 2057, 87));
add(new Person("Shawn", "Randall", "Java programmer", "male", 3120, 99));
add(new Person("Jayden", "Corrina", "Java programmer", "female", 345, 25));
add(new Person("Palmer", "Dene", "Java programmer", "male", 3375, 14));
add(new Person("Addison", "Pam", "Java programmer", "female", 3426, 20));
}
};
为什么使用数值流
在文章开头,我就提到我们平时使用最多的就是对象流,那为什么JAVA8
还要提供数值流呢?
首先抛出一个示例研究分析一下:
//2.1.1 计算集合中的所有人的薪资总数
int reduce = personList.stream()
// private int salary,age;map()方法通过方法的引用获取的每一个人的薪资都是int类型的
.map(Person::getSalary)
.reduce(0, Integer::sum);
这是一个简单的对集合流的操作:计算所有人薪资总和
我们将这一段代码分为两部分进行分析:
第一部分:map()取值操作,将所有对象的薪资取出
首先是取值操作:
@Data
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String firstName, lastName, job, gender;
private int salary,age;
}
通过插件获取的属性值是`int`类型的。
我们在看map的源码:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
根据map操作的源码我们可以看到,map操作后返回的是Integer类型的对象流Stream<R>
那么这里就反应出一个问题:我们传入的是int基本数据类型,而map操作之后返回的却是Integer包装类型的对象流。也就是说这其中暗含了一步封箱操作
第二部分:reduce()规约操作,通过引用sum方法进行求和
首先我们sum求和方法的源码:
public static int sum(int a, int b) {
return a + b;
}
根据sum的源码我们发现Integer::sum 这个方法引用,它引用的方法的参数和返回值都是int。
因此规约求和后的返回值确实是int。
这就反应出第二个问题:map操作后,结果为Integer对象流Stream ,然后要进行reduce里的求和操作就需要先进行拆箱操作(integer->int),返回int类型。
下面我们用数值流实现这个需求:
//2.1.2 优化上面的操作(主要是减少封箱拆箱操作)
int sum = personList.stream()
.mapToInt(Person::getSalary) // IntStream mapToInt(ToIntFunction<? super T> mapper);
.sum(); // int sum();
然后将两者进行对比,使用数值流操作减少了中间的反复拆箱和封箱操作,而这些操作都是需要耗费内存资源的,同时对象流stream 也没有数值流这种简单直接的求和方法 sum()可调用(当然这只是其中一个方法)。这就是为什么java8 专门提供了数值流给我们使用。
小结:
使用对象流Stream操作的弊端,就是会频繁的进行封箱,拆箱,严重浪费了内存资源,
使用数值流可以减少这一行为,提高内存利用率。
* 另外操作对象流也没有数值流这样简单直接,也这是java8提供数值流的原因
Numeric streams 数值流的使用
java8 引入了三个基本数据类型流接口:IntStream、DoubleStream和LongStream
每个接口分别对应流中的元素为:int 、long和double
,从而避免减少出现暗含的封箱、拆箱成本。同时每个接口都带来了进行常用数值reduce
操作的新方法,比如对数值流求和的sum(如果流是空的,sum返回值为0)
,还有max、min、average
等方法。
1. 对象流转数值流
将对象流转换为数值流的常用方法是mapToInt、mapToDouble和mapToLong
//3.1 对象流转数值流 将对象流转换为数值流的常用方法是mapToInt、mapToDouble和mapToLong为例:
Integer[] arr = {1,2,3,4,5,6,7,8,9};
Double[] arrs = {1.1,2.5,3.6,4.4,5.5,6.4,7.1,8.1,9.7};
//3.1.1 mapToInt()方法
IntStream intStream = Arrays.stream(arr).mapToInt(e -> e.intValue());
//3.1.2 mapToDouble()方法
DoubleStream doubleStream = Arrays.stream(arrs).mapToDouble(e -> e.doubleValue());
//3.1.3 mapToLong()方法
LongStream longStream = Arrays.stream(arr).mapToLong(e -> e.longValue());
2. 数值流转为对象流
为什么:为数值流里面的方法受众面可能比较窄,方法拓展性也弱
/*3.2 数值流转为对象流 boxed()方法
* 为什么:为数值流里面的方法受众面可能比较窄,方法拓展性也弱
*/
Integer[] arrBoxed = {1,2,3,4,5,6,7,8,9};
Stream<Integer> objStream = Arrays.stream(arrBoxed);//返回对象流
IntStream intStream1 = objStream.mapToInt(e -> e.intValue());//对象流---->数值流
Stream<Integer> boxed = intStream1.boxed();//数值流---->对象流
//优化
Arrays.stream(arrBoxed).mapToInt(e -> e.intValue()).boxed();
3. 默认值OptionalInt
Optional
这个类,这是一个可以表示值存在或不存在的java容器
。针对三种数值流,也分别有一个Optional数值类型版本:OptionalInt、OptionalDouble和OptionalLong
。Optional详解
这个的作用体现在哪里呢?看下面这个例子,它的流是空的,但是我们对这个流进行reduce操作时,我们给了它一个初始参数 0,并希望进一步求得到这个流中的最大值,运行程序结果为:0。那么我们这个流中的最大值就是0了吗?这个结果是我们想要的吗?显然不是,事实是这个流不存在最大值,因为他是空的,输出的0不过是我们reduce
的一个初始参数0。
Integer reduce1 = Arrays.stream(arrNull).reduce(0, Integer::max);
//打印结果为:0 (需要注意reduce的初始化参数为0)
当你需要考虑到流没有最大值的情况时(当然这只是一种需求情况而已),你就可以显式处理OptionalInt
,去定义一个默认值,避免出现上面那个例子的混淆结果:
//3.3 默认值OptionalInt
Integer[] arrNull = {};
Stream<Integer> stream = Arrays.stream(arrNull);//对象流
OptionalInt max1 = stream.mapToInt(i -> i.intValue()).max();//转为数值流求最大值
int reduce = max1.orElse(10000000);//如果max方法返回值为空,则返回100000000
System.out.println(reduce);
int i1 = Arrays.stream(arrNull).mapToInt(i -> i.intValue()).max().orElse(10000000);
System.out.printf("优化:"+i1);
4. 数值范围
Java8中还引入了两个可以用于IntStream和LongStream的静态方法帮助生成这种范围:range和rangeClosed
//3.4 数值范围 Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed
System.out.println("rangeClosed则包含结束值:");
//rangeClosed() 第一个参数接受起始值,第二个参数接受结束值
IntStream.rangeClosed(1, 100).filter(s1 -> s1 % 2 == 0).forEach(e-> System.out.printf("%s%s"," ",e));
System.out.println("range是不包含结束值的:");
//range()第一个参数接受起始值,第二个参数接受结束值
IntStream.range(1, 100).filter(s1 -> s1 % 2 == 0).forEach(e-> System.out.printf("%s%s"," ",e));
结束语
辛苦搬砖的打工人,明天休息,面临着找地方搬家,太难了。
继续保持搬砖精神,
推荐博客:
数值流与对象流的转化及其方法使用:https://blog.csdn.net/aigoV/article/details/102897762