一、基本數據類型與基本數據的包裝類
在Java編程語言中,int、long和double等基本數據類型都各有它們的包裝類型Integer、Long和Double。
基本數據類型是Java程序語言內置的數據類型,可直接使用。
而包裝類型則歸屬于普通的Java類,是對基本數據類型的封裝,以便于把基本數據類型作為對象操作。
在集合框架中廣泛使用泛型來定義集合,集合中只允許插入對象元素,例如列表List<E>,列表中只能保存Integer數據,不能保存int數據,當插入int數據時將自動裝箱包裝為Integer。集合框架中的所有集合都不支持直接處理基本數據類型的數據。
但是,基本數據類型的包裝類型也有其缺點,包裝類型所占存儲空間比較大。例如,整型數在內存中占用4個字節存儲空間,包裝類型的整型對象卻要占用16個字節。這種情況在數組等集合類數據結構中更嚴重,整型數組中的每個元素只占用基本數據類型的內存空間,而整型對象數組的每個元素都需要額外增加一個指針,這個指針指向保存在堆內存中的整型對象。在最極端的情況,同樣大小的數組,Integer[]要比int[]占用的內存空間大6倍。
二、對象流(Stream<T>)和基本數據類型流
在 Java 8 中,流(Stream)可以分為兩種類型:對象流(Stream<T>)和基本數據類型流。
前面介紹的都是對象流,例如,對象流Stream<Integer>必須將整型數包裝成Integer對象。
在對象流中基本數據類型數據元素的裝箱和拆箱,都需要額外的計算開銷。對于大規模的數值運算情形,裝箱和拆箱的計算開銷,以及包裝類型占用的額外內存空間,會明顯影響程序的性能。
為了提高基本數據類型在大規模數值運算中的效率,Java 8 在Stream API中定義了IntStream、LongStream和DoubleStream三種專門用于直接存儲基本類型數據的基本數據類型流。這三種基本數據類型流,雖然流的名稱上用的是Int、Long、Double,好像是包裝對象,實際上它們處理的是基本數據類型int、long和double的數據。由于不需要裝箱和拆箱,在大規模的數值計算中,程序處理性能會有明顯提升。
對于七種基本數據類型,Java 庫中只定義了IntStream、LongStream和DoubleStream三種基本數據類型流,因為它們在數值計算中應用最廣泛。大數據處理情形,如要處理存儲short、char、byte和boolean,可使用IntStream。而對于float,可使用DoubleStream。
IntStream, LongStream 和 DoubleStream 是專門處理基本類型 int, long, 和 double 數據的流。這些流是為了提高性能而設計的,因為它們避免了裝箱/拆箱操作,這對于大數據量的處理尤其重要。
三、基本數據類型流詳解
基本數據類型流又細分為下面三種:IntStream, LongStream 和 DoubleStream
IntStream、LongStream和DoubleStream三種基本類型數據流具有類似的行為和方法,下面就以IntStream為例進行介紹,其他二種用法類似,以此類推。
整型數據流 IntStream
IntStream 是一種特殊的流,它用于處理 int 類型的數據。它提供了一系列方法來高效地操作整數序列。
(一)、IntStream類創建 IntStream流的各種方式:
1,使用 IntStream.range 或 IntStream.rangeClosed 方法來生成一個指定范圍內的整數序列。
2,使用 IntStream.iterate 方法來迭代生成一個無限或有限的整數序列。
3,使用 IntStream.generate 方法使用產生器來生成一個無限或有限的整數序列。
4,使用IntStream.of方法 提供指定的一組值來創建stream。
5,使用IntStream.empty方法 可創建一個不包含任何元素的空流。
6,使用IntStream.builder方法 類似于構造器設計模式的stream創建方式,可以通過構造器添加若干個元素,然后通過build方法獲取流對象。
創建 IntStream例程:
public static void crtIntStream() {// 生成從0到9的整數序列IntStream.range(0, 10).forEach(System.out::println);// 生成從1到10(包括10)的整數序列IntStream.rangeClosed(1, 10).filter(n -> n % 2 != 0).forEach(System.out::println);// 生成一個無限的偶數序列IntStream.iterate(0, n -> n + 2).limit(5).forEach(System.out::println);// 借助隨機發生器利用產生器生成一個無限的隨機整型數序列Random random = new Random();IntStream.generate(() -> random.nextInt(1000)).limit(10).forEach(n -> System.out.println(n));//Math::random返回double型的隨機數DoubleStream.generate(Math::random).limit(10).sorted().forEach(System.out::println);IntStream.of(9, 90, 876, 12).forEach(v -> System.out.println(v));IntStream.builder().add(90).add(87).build().forEach(v -> System.out.println(v));}
(二)、其他創建 IntStream 流的方式:
還有很多其他創建 IntStream 流的方法,我們這里介紹兩種:
- 用隨機數類Random創建 IntStream 流
函數式編程的第一個例程中用隨機數類Random的ints()方法生成的就是IntStream流,類似地類Random也有方法longs()、方法doubles()可生成LongStream和DoubleStream流。下面是一個示例:
//用隨機數類Random的ints()方法創建IntStream流,并收集到int數組int[] intArray = new Random(61).ints(10).distinct().limit(10).sorted().toArray();
- 用字符串( String)創建 IntStream 流
字符串( String)創建 IntStream 流有兩種方法,分別是 java.lang.CharSequence 接口定義的默認方法 chars 和 codePoints。詳細請見后文。
(三)、 IntStream 流的基本用法:
Java語言為基本數據類型流設計了一套類似的操作,我們下面主要以IntStream為例進行演示,其他兩種基本數據類型流也有類似操作,其用法也類似。
基本數據類型流也有和普通對象流類似的一些操作方法,例如map、filter、sorted等,但性能更優。請先看幾個示例:
IntStream.of (1, 2, 3, 4, 9).filter(e -> e > 2).peek(e -> System.out.println("符合條件的值: " + e)).map(e -> e * e).peek(e -> System.out.println("映射為平方值: " + e)).sum();
下面是一個使用 IntStream 的完整示例,該示例展示了如何生成一個整數序列,并計算其中所有數字的平方和和極值。
public class IntStreamExample {public static void main(String[] args) {int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};// 打印所有的數字System.out.println("整數序列:");IntStream.of(numbers).forEach(System.out::println);// 計算所有數字的平方和int sumOfSquares = IntStream.of(numbers).map(n -> n * n).sum();System.out.println("整數序列的平方和: " + sumOfSquares);// 找到最大的數字OptionalInt maxNumber = IntStream.of(numbers).max();System.out.println("最大整型數: " + (maxNumber.isPresent() ? maxNumber.getAsInt() : "不存在"));// 找到最小的數字OptionalInt minNumber = IntStream.of(numbers).min();System.out.println("最小整型數: " + (minNumber.isPresent() ? minNumber.getAsInt() : "不存在"));}
}
(四)、基本數據類型流的reduce規約操作
按照特定規則處理每個元素,然后返回處理后的結果:
public static void testIntStreamReduce(){//單個參數的reduce操作//求1到100的和System.out.println("1-100的和為:"+IntStream.rangeClosed(1,100).reduce((x,y)->x+y).getAsInt());//orElse和orElseGet可以防止返回值為空,如果為空則默認返回0System.out.println(IntStream.of(1,5,19).reduce((x, y) -> x + y).orElse(0));System.out.println(IntStream.of(1,5,19).reduce((x, y) -> x + y).orElseGet(() -> 0));System.out.println(IntStream.of(1,5,19).reduce((x, y) -> x + y).orElseThrow(()->new RuntimeException("參數為空,返回值為空")));//兩個參數的reduce操作IntStream intStream = IntStream.of (1, 2, 3, 4, 9);int sum = intStream.reduce(0, (a, b) -> a+b);int total = intStream.reduce(0, Integer::sum);}
(五)、基本數據類型流的數學計算與專用的SummaryStatistics 匯總統計
Java語言為基本數據類型流內置了一些實用的方法:例如,都有內置的規約操作,包括average、count、max、min、sum;還有最常用的匯總統計SummaryStatistics()方法,這個方法可得到一組統計數據:元素個數、匯總值、最大值、最小值和平均值。當然了,如果你不需要這組統計數值,只需要其中幾個,也可直接調用內置的規約操作min、max、average或sum方法獲得單個的統計值。
下面的例程演示基本數據類型流使用SummaryStatistics方式來進行統計數據。
public static void testMathOpt() {System.out.println("總和:"+IntStream.of(10, 20, 30, 40).sum());System.out.println("最大值:"+IntStream.of(10, 20, 30, 40).max().getAsInt());System.out.println("最小值:"+IntStream.of(10, 20, 30, 40).min().getAsInt());System.out.println("元素個數:"+IntStream.of(10, 20, 30, 40).count());System.out.println("平均值:"+IntStream.of(10, 20, 30, 40).average().getAsDouble());System.out.println("---使用summaryStatistics進行數學運算:---");IntSummaryStatistics summaryStatistics = IntStream.of(10, 20, 30, 40).summaryStatistics();System.out.println("總和:" + summaryStatistics.getSum());System.out.println("最大值:" + summaryStatistics.getMax());System.out.println("最小值:" + summaryStatistics.getMin());System.out.println("元素個數:" + summaryStatistics.getCount());System.out.println("平均值:" + summaryStatistics.getAverage());}
測試結果:
(六)、流的裝箱與拆箱
流(Stream)通常是對象流(Stream<T>),本文則主要介紹的是基本數據類型流(IntStream, LongStream 和 DoubleStream)當把基本數據類型流轉換為對象流(Stream<T>)我們常稱這為流的裝箱;反之,把對象流(Stream<T>)轉換為基本數據類型流(IntStream, LongStream 和 DoubleStream)則稱之為流的拆箱。
對象流(Stream<T>)和基本數據類型流(IntStream, LongStream 和 DoubleStream)各有優缺點:對象流(Stream)可使用的操作工具更豐富;而基本數據類型流在大規模數據運算場景時運算效率更高。
在處理對象流(Stream<T>)的時候,可以利用 Collectors 類的收集器收集結果到集合,例如,將字符串流收集到列表 List ,這種方式是沒有問題的。但基本數據類型流默認只能收集到數組。
基本數據類型流的裝箱方法
基本類型流的裝箱操作,集合只能處理對象數據,對象流可以利用Collectors類的收集器來收集,但基本數據類型流就無法用收集器來處理。遇到這種情形若要用收集器來收集數據,可把基本數據類型流(如int類型)裝箱轉換為包裝類型(Integer型)的對象流。
叁種裝箱方法:mapToObj() 、 boxed()和三叁數的collect(supplier,accumulator,combiner)
(1)Stream<Integer> boxed() 用裝箱方法boxed()把基本類型流轉換為對象流。
(2)<U> Stream<U> mapToObj(IntFunction<? extends U> mapper) 用方法mapToObj映射為對象流。
(3)利用三參數的收集器方法collect(supplier,accumulator,combiner),這個方法第一個參數是一個供給器,相當于初始化(創建)一個容器;第二個參數是累加器,相當于給初始化的供給器添加一個元素;第三個參數是組合器,相當于將這些元素全部組合到一個容器。
流的裝箱:使用 boxed 方法將 IntStream 轉換為 Stream<Integer>,然后可用Collect()收集操作收集結果到列表中。
【例程10-34】基本數據類型流三種包裝為對象流例程BoxedTest
例程BoxedTest.java源代碼開始:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
/**** @author QiuGen* @description 基本數據類型流的三種裝箱例程BoxedTest* @date 2024/8/20* ***/
public class BoxedTest {public static void main(String[] args) {
//IntStream.of(1, 2, 3, 4).collect(Collectors.toList()); //編譯器通不過。IntStream.of(1, 2, 3, 4).boxed() //裝箱為對象流
.collect(Collectors.toList()).forEach(System.out::println);IntStream.of(25,50,75,95).mapToObj(Integer::new) //映射為對象流.collect(Collectors.toList()).forEach(System.out::println);List<Double> list = DoubleStream.of(10.60,20.10,30.50) //使用三參數收集方法.collect(ArrayList<Double>::new, ArrayList::add, ArrayList::addAll); list.forEach(System.out::println);}
} //例程BoxedTest.java源碼結束。
流的拆箱方法:
從對象流拆箱轉換為基本數據類型流,可以用方法flatMapToxxx(其中的xxx表示基本數據類型,下同)和方法mapToxxx從普通的對象流轉換為基本數據類型流。以下是這六種拆箱方法的方法簽名:
IntStream mapToInt(ToIntFunction<? super T> mapper)LongStream mapToLong(ToLongFunction<? super T> mapper)DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper) LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper)DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream>
使用 mapToInt 方法將 Stream<T> 轉換為 IntStream。其他兩個拆箱方法也實現類似功能。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);IntStream intStream = list.stream().mapToInt(Integer::intValue);
flatMapToInt是一個非常有用的流拆箱操作,它可以將嵌套的流結構"扁平化"為一維的IntStream。下面是一個演示例程FlatMapToIntExample :
package stream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class FlatMapToIntExample {public static void main(String[] args) {// 創建一個包含多個整數列表的列表List<List<Integer>> listOfLists = Arrays.asList(Arrays.asList(1, 2, 3),Arrays.asList(4, 5),Arrays.asList(6, 7, 8, 9));// 使用flatMapToInt將所有整數扁平化為一個IntStreamIntStream intStream = listOfLists.stream().flatMapToInt(list -> list.stream().mapToInt(Integer::intValue));// 輸出結果intStream.forEach(System.out::println);}
} //演示例程FlatMapToIntExample
請再看一個完整的拆箱示例:
public static void unBoxedIntStream() { /***拆箱***/List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);List<Integer> list2 = Arrays.asList(6, 7, 8, 9, 10);// 將兩個列表轉換為 IntStreamIntStream stream1 = list1.stream().mapToInt(Integer::intValue);IntStream stream2 = list2.stream().mapToInt(Integer::intValue);// 合并兩個 IntStreamIntStream combinedStream = IntStream.concat(stream1, stream2);// 計算總和int sum = combinedStream.sum();System.out.println("匯總: " + sum);List<Integer> list = Arrays.asList(8, 9, 10, 3, 4, 5);IntStream intStream = list.stream().mapToInt(Integer::intValue);// 過濾出大于 5 的數IntStream filteredStream = intStream.filter(x -> x > 5);list = filteredStream.boxed().collect(Collectors.toList());System.out.println("過濾數據: " + list);}
還可用IntStream的asLongStream()方法,把IntStream流轉換為LongStream。
基本數據類型流之間轉換的例程:
//把IntStream流轉換為LongStream的兩個版本的示例public static void chgIntStreamToLongStream() {LongStream longStream = IntStream.rangeClosed(1, 5).mapToLong(v -> v * 2);longStream.forEach(v1 -> System.out.println(v1 + " -> " + ((Object)v1).getClass()));LongStream lnStream = IntStream.rangeClosed(1, 10).asLongStream(); //轉換為LongStream}
(七)、從字符串創建基本數據類型流
字符串( String)創建 IntStream 流有兩種方法,分別是 java.lang.CharSequence 接口定義的默認方法 chars 和 codePoints。
這兩個方法的不同這處:
- 方法 codePoints() 返回的是碼點值,表示Unicode字符集中的一個字符。
- 方法 chars()返回的是一個碼元值。在Java中,字符是以兩個字節(16位)的UTF-16編碼存儲的。UTF-16編碼的碼元長度是雙字節(16bit位)長。UTF-16編碼,對于基本多語言平面(BMP)中的字符用一個碼元就可以表示一個碼點;但對于補充平面中的字符則需要使用兩個碼元來表示一個碼點(字符)。
public static void strAndIntStream() {// 增加 笑臉字符😊String msg = "Welcome to Java8,歡迎光臨!😊".codePoints()//轉換成IntStream流.collect(StringBuffer::new,StringBuffer::appendCodePoint,StringBuffer::append)//將流轉換為字符串.toString();System.out.println("msg: " + msg);IntStream strStream = "Welcome to Java8,中國崛起!😊".chars();//轉換成IntStream流String message = strStream.collect(StringBuffer::new,StringBuffer::appendCodePoint,StringBuffer::append)//將流轉換為字符串.toString();System.out.println("message: " + message);}
程序測試效果圖:
碼點(codePoint)整型流的一個演示例程:
public class MultiByteCodePointExample {public static void main(String[] args) {// 選擇一個多字節字符String emoji = "😊"; // 這個字符串有一個多字節字符int codePoint = emoji.codePointAt(0);// 使用toChars將碼點轉換為字符char[] chars = Character.toChars(codePoint);System.out.printf("Emoji: %s, Codepoint: U+%04X, Characters: %s%n", emoji, codePoint, new String(chars));String symbols = "𝕆"; //數學符號的碼點值 codePoint = 0x1D546; //數學符號的碼點值 chars = Character.toChars(codePoint);System.out.printf("數學符號: %s, Codepoint: U+%04X, Characters: %s%n", symbols, codePoint, new String(chars)); }}
程序測試結果:
(八)、長整型數據流 LongStream
LongStream 與 IntStream 類似,但是它處理的是 long 類型的數據。
創建 LongStream
使用 LongStream.range 或 LongStream.rangeClosed 方法來生成一個指定范圍內的長整數序列。
使用 LongStream.iterate 方法來生成一個無限或有限的長整數序列。
使用 LongStream.generate 方法來生成一個無限或有限的長整數序列。
// 生成從0L到9L的長整數序列LongStream.range(0L, 10L).forEach(System.out::println);// 生成從1L到10L(包括10L)的長整數序列LongStream.rangeClosed(1L, 10L).forEach(System.out::println);// 生成一個無限的偶數長整數序列LongStream.iterate(0L, n -> n + 2).limit(5).forEach(System.out::println);
【例程10-33】斐波那契數列函數式編程的算法版本FibonacciStream
例程FibonacciStream.java是斐波那契數列函數式編程的算法版本:
package fibonacci;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.LongStream;
import java.util.stream.Stream;
/**** @author QiuGen* @description 斐波那契數列的函數式編程的算法* @date 2024/8/20* ***/
public class FibonacciStream {/***迭代生成斐波那契數列的無限流***/public static Stream<long[]> fibStream( ) {Stream<long[]> fibStream = Stream.iterate(new long[]{0, 1},fib -> new long[]{fib[1], fib[0] + fib[1]});return fibStream;}/***用Collect收集結果***/public static void fibStreamA( ) {List<Long> lst = fibStream().limit(30) //前30項.map(fib -> fib[0]).collect(ArrayList::new, ArrayList::add,ArrayList::addAll);lst.forEach(System.out::println);}/**轉換為LongStream用toArray()收集結果為long[],并打印結果**/public static void fibStreamB( ) {LongStream fibLongStream = fibStream().limit(30) //前30項
.map(fib -> fib[0]).mapToLong(k->k);long fib[] = fibLongStream.toArray();for (int i = 0; i < fib.length; i++) {System.out.println(fib[i]);}}public static void main(String[] args) {fibStreamA();fibStreamB();}
} //斐波那契數列的函數式編程的算法版本FibonacciStream.java。
請注意函數fibStreamB()的處理方式,只有基本數據類型的流才能用toArray()方法把結果收集為基本數據類型的數組。對象流只能收集為對象數組。
(九)、雙精度型數據流 DoubleStream
DoubleStream 處理 double 類型的數據。
創建 DoubleStream
使用 DoubleStream.iterate 方法來生成一個無限或有限的雙精度浮點數序列。
使用 DoubleStream.generate 方法來生成一個無限或有限的雙精度浮點數序列。
// 生成一個無限的雙精度浮點數序列DoubleStream.iterate(0.0, n -> n + 0.1).limit(5).forEach(System.out::println);//Math::random返回double型的隨機數DoubleStream.generate(Math::random).limit(10).sorted().forEach(System.out::println);