Stream(二)—數值流與對象流的轉化及其方法使用
什么是數值流,什么是對象流?
在上篇博客中,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
下面我們用數值流實現這個需求:
//2.1.2 優化上面的操作(主要是減少封箱拆箱操作)
int sum = personList.stream()
.mapToInt(Person::getSalary) // IntStream mapToInt(ToIntFunction<? super T> mapper);
.sum(); // int sum();
然后將兩者進行對比,使用數值流操作減少了中間的反復拆箱和封箱操作,而這些操作都是需要耗費內存資源的,同時對象流stream
小結:
\(~~~~~~~\)* 使用對象流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