本篇內(nèi)容介紹了“Java Stream流式編程常見的坑有哪些”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)建站服務(wù)項目包括扶余網(wǎng)站建設(shè)、扶余網(wǎng)站制作、扶余網(wǎng)頁制作以及扶余網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,扶余網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到扶余省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
Stream是Java8新增的一個接口,允許以聲明性方式處理數(shù)據(jù)集合。Stream不是一個集合類型不保存數(shù)據(jù),可以把它看作是遍歷數(shù)據(jù)集合的高級迭代器(Iterator)。
Stream操作可以像Builder一樣逐步疊加,形成一條流水線。流水線一般由數(shù)據(jù)源+零或者多個中間操作+一個終端操作所構(gòu)成。中間操作可以將流轉(zhuǎn)換成另外一個流,比如使用filter過濾元素,使用map映射提取值。
Stream與lambda表達式密不可分,本文默認(rèn)你已經(jīng)掌握了lambda基礎(chǔ)知識。
只能遍歷(消費)一次。Stream實例只能遍歷一次,終端操作后一次遍歷就結(jié)束,再次遍歷需要重新生成實例,這一點類似于Iterator迭代器。
保護數(shù)據(jù)源。對Stream中任何元素的修改都不會導(dǎo)致數(shù)據(jù)源被修改,比如過濾刪除流中的一個元素,再次遍歷該數(shù)據(jù)源依然可以獲取該元素。
懶。filter, map 操作串聯(lián)起來形成一系列中間運算,如果沒有一個終端操作(如collect)這些中間運算永遠也不會被執(zhí)行。
(1)使用指定值創(chuàng)建Stream實例
// of為Stream的靜態(tài)方法 StreamstrStream = Stream.of("hello", "java8", "stream"); // 或者使用基本類型流 IntStream intStream = IntStream.of(1, 2, 3); 復(fù)制代碼
(2)使用集合創(chuàng)建Stream實例(常用方式)
// 使用guava庫,初始化一個不可變的list對象 ImmutableListintegers = ImmutableList.of(1, 2, 3); // List接口繼承Collection接口,java8在Collection接口中添加了stream方法 Stream stream = integers.stream(); 復(fù)制代碼
(3)使用數(shù)組創(chuàng)建Stream實例
// 初始化一個數(shù)組 Integer[] array = {1, 2, 3}; // 使用Arrays的靜態(tài)方法stream Streamstream = Arrays.stream(array); 復(fù)制代碼
(4)使用生成器創(chuàng)建Stream實例
// 隨機生成100個整數(shù) Random random = new Random(); // 加上limit否則就是無限流了 Streamstream = Stream.generate(random::nextInt).limit(100); 復(fù)制代碼
(5)使用迭代器創(chuàng)建Stream實例
// 生成100個奇數(shù),加上limit否則就是無限流了 Streamstream = Stream.iterate(1, n -> n + 2).limit(100); stream.forEach(System.out::println); 復(fù)制代碼
(6)使用IO接口創(chuàng)建Stream實例
// 獲取指定路徑下文件信息,list方法返回Stream類型 StreampathStream = Files.list(Paths.get("/")); 復(fù)制代碼
Stream接口中定義了很多操作,大致可以分為兩大類,一類是中間操作,另一類是終端操作;
(1)中間操作
中間操作會返回另外一個流,多個中間操作可以連接起來形成一個查詢。
中間操作有惰性,如果流上沒有一個終端操作,那么中間操作是不會做任何處理的。
下面介紹常用的中間操作:
map操作
map是將輸入流中每一個元素映射為另一個元素形成輸出流。
// 初始化一個不可變字符串 Listwords = ImmutableList.of("hello", "java8", "stream"); // 計算列表中每個單詞的長度 List list = words.stream() .map(String::length) .collect(Collectors.toList()); // output: 5 5 6 list.forEach(System.out::println); 復(fù)制代碼
flatMap操作
Listlist1 = words.stream() .map(word -> word.split("-")) .collect(Collectors.toList()); // output: [Ljava.lang.String;@59f95c5d, // [Ljava.lang.String;@5ccd43c2 list1.forEach(System.out::println); 復(fù)制代碼
哪里?你預(yù)期是List, 返回卻是List
這個時候你可以想到要將數(shù)組轉(zhuǎn)成stream, 于是有了第二個版本
Stream> arrStream = words.stream() .map(word -> word.split("-")) .map(Arrays::stream); // output: java.util.stream.ReferencePipeline$Head@2c13da15, // java.util.stream.ReferencePipeline$Head@77556fd arrStream.forEach(System.out::println); 復(fù)制代碼
還是不對,這個問題使用flatMap扁平流可以解決,flatMap將流中每個元素取出來轉(zhuǎn)成另外一個輸出流
StreamstrStream = words.stream() .map(word -> word.split("-")) .flatMap(Arrays::stream) .distinct(); // output: hello java8 stream strStream.forEach(System.out::println); 復(fù)制代碼
filter操作
filter接收Predicate對象,按條件過濾,符合條件的元素生成另外一個流。
// 過濾出單詞長度大于5的單詞,并打印出來 Listwords = ImmutableList.of("hello", "java8", "hello", "stream"); words.stream() .filter(word -> word.length() > 5) .collect(Collectors.toList()) .forEach(System.out::println); // output: stream 復(fù)制代碼
(2)終端操作
終端操作將stream流轉(zhuǎn)成具體的返回值,比如List,Integer等。常見的終端操作有:foreach, min, max, count等。
foreach很常見了,下面舉一個max的例子。
// 找出最大的值 Listintegers = Arrays.asList(6, 20, 19); integers.stream() .max(Integer::compareTo) .ifPresent(System.out::println); // output: 20 復(fù)制代碼
假如有一個需求:過濾出年齡大于20歲并且分?jǐn)?shù)大于95的學(xué)生。
使用for循環(huán)寫法:
private ListgetStudents() { Student s1 = new Student("xiaoli", 18, 95); Student s2 = new Student("xiaoming", 21, 100); Student s3 = new Student("xiaohua", 19, 98); List studentList = Lists.newArrayList(); studentList.add(s1); studentList.add(s2); studentList.add(s3); return studentList; } public void refactorBefore() { List studentList = getStudents(); // 使用臨時list List resultList = Lists.newArrayList(); for (Student s : studentList) { if (s.getAge() > 20 && s.getScore() > 95) { resultList.add(s); } } // output: Student{name=xiaoming, age=21, score=100} resultList.forEach(System.out::println); } 復(fù)制代碼
使用for循環(huán)會初始化一個臨時list用來存放最終的結(jié)果,整體看起來不夠優(yōu)雅和簡潔。
使用lambda和stream重構(gòu)后:
public void refactorAfter() { ListstudentLists = getStudents(); // output: Student{name=xiaoming, age=21, score=100} studentLists.stream().filter(this::filterStudents).forEach(System.out::println); } private boolean filterStudents(Student student) { // 過濾出年齡大于20歲并且分?jǐn)?shù)大于95的學(xué)生 return student.getAge() > 20 && student.getScore() > 95; } 復(fù)制代碼
使用filter和方法引用使代碼清晰明了,也不用聲明一個臨時list,非常方便。
(1)誤區(qū)一:重復(fù)消費stream對象
stream對象一旦被消費,不能再次重復(fù)消費。
Liststrings = Arrays.asList("hello", "java8", "stream"); Stream stream = strings.stream(); stream.forEach(System.out::println); // ok stream.forEach(System.out::println); // IllegalStateException 復(fù)制代碼
上述代碼執(zhí)行后報錯:
java.lang.IllegalStateException: stream has already been operated upon or closed
(2)誤區(qū)二:修改數(shù)據(jù)源
在流操作的過程中嘗試添加新的string對象,結(jié)果報錯:
Liststrings = Arrays.asList("hello", "java8", "stream"); // expect: HELLO JAVA8 STREAM WORLD, but throw UnsupportedOperationException strings.stream() .map(s -> { strings.add("world"); return s.toUpperCase(); }).forEach(System.out::println); 復(fù)制代碼
注意:一定不要在操作流的過程中修改數(shù)據(jù)源。
“Java Stream流式編程常見的坑有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!