核心概述:在開發中,我們經常使用匿名内部類作為實參傳遞參數,我們可以發現匿名内部類的格式比較繁瑣,那麼如何簡化呢?本篇我們将會學習到Lambda表達式來幫助我們解決問題。另外我們也将學習與Lambda表達式相關的函數式接口,以及Stream流。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SZlRmZ3ITYyQzM5IDOyMDO3gDMhVzNkVmN3gjNlhjNz8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLlNWa2JXZz1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
第一章:Lambda表達式
1.1-函數式程式設計介紹(了解)
在數學中,函數就是有輸入量、輸出量的一套計算方案,也就是“拿什麼東西做什麼事情”。相對而言,面向對象過分強調“必須通過對象的形式來做事情”,而函數式思想則盡量忽略面向對象的複雜文法——強調做什麼,而不是以什麼形式做。
面向對象的思想: 做一件事情,找一個能解決這個事情的對象,調用對象的方法,完成事情。
函數式程式設計思想: 隻要能擷取到結果,誰去做的,怎麼做的都不重要,重視的是結果,不重視過程 。
1.2-為什麼要用Lambda表達式(了解)
以Runnable為例
當需要啟動一個線程去完成任務時,通常會通過
java.lang.Runnable
接口來定義任務内容,并使用
java.lang.Thread
類來啟動該線程。
傳統寫法:
public class Demo01Runnable {
public static void main(String[] args) {
// 匿名内部類
Runnable task = new Runnable() {
@Override
public void run() {
// 覆寫重寫抽象方法
System.out.println("多線程任務執行!");
}
};
new Thread(task).start(); // 啟動線程
}
}
本着“一切皆對象”的思想,這種做法是無可厚非的:首先建立一個 Runnable 接口的匿名内部類對象來指定任務内 容,再将其交給一個線程來啟動。
傳統寫法分析:
- Thread 類需要 Runnable 接口作為參數,其中的抽象 run 方法是用來指定線程任務内容的核心;
- 為了指定 run 的方法體,不得不需要 Runnable 接口的實作類;
- 為了省去定義一個 RunnableImpl 實作類的麻煩,不得不使用匿名内部類;
- 必須覆寫重寫抽象 run 方法,是以方法名稱、方法參數、方法傳回值不得不再寫一遍,且不能寫錯;
- 而實際上,似乎隻有方法體才是關鍵所在。
程式設計思想的轉變
我們真的希望建立一個匿名内部類對象嗎?不。我們隻是為了做這件事情而不得不建立一個對象。我們真正希望做的事情是:将 run 方法體内的代碼傳遞給 Thread 類知曉。
傳遞一段代碼——這才是我們真正的目的。而建立對象隻是受限于面向對象文法而不得不采取的一種手段方式。
那有沒有更加簡單的辦法?如果我們将關注點從“怎麼做”回歸到“做什麼”的本質上,就會發現隻要能夠更好地達 到目的,過程與形式其實并不重要
Lambda優化體驗
借助Java 8的全新文法,上述
Runnable
接口的匿名内部類寫法可以通過更簡單的Lambda表達式達到等效:
public class Demo01LambdaRunnable {
public static void main(String[] args) {
new Thread(() -> System.out.println("多線程任務執行!")).start(); // 啟動線程
}
這段代碼和剛才的執行效果是完全一樣的,可以在1.8或更高的編譯級别下通過。從代碼的語義中可以看出:我們啟動了一個線程,而線程任務的内容以一種更加簡潔的形式被指定。
不再有“不得不建立接口對象”的束縛,不再有“抽象方法覆寫重寫”的負擔,就是這麼簡單!
1.3-Lambda格式(重要)
格式
- Lambda省去面向對象的條條框框,格式由3個部分組成:
- 一些參數
- 一個箭頭
- 一段代碼
- 标準格式:
(參數類型 參數名稱) ‐> { 代碼語句 }
- 格式說明:
- 小括号内的文法與傳統方法參數清單一緻:無參數則留白;多個參數則用逗号分隔。
-
是新引入的文法格式,代表指向動作。->
- 大括号内的文法與傳統方法體要求基本一緻。
無參無傳回值代碼,匿名内部類與lambda對比
代碼:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多線程任務執行!");
}
}).start();
仔細分析該代碼中,
Runnable
接口隻有一個
run
方法的定義:
-
public abstract void run();
即制定了一種做事情的方案(其實就是一個方法):
- 無參數:不需要任何條件即可執行該方案。
- 無傳回值:該方案不産生任何結果。
- 代碼塊(方法體):該方案的具體執行步驟。
同樣的語義展現在
Lambda
文法中,要更加簡單:
代碼說明:
- 前面的一對小括号即
方法的參數(無),代表不需要任何條件;run
- 中間的一個箭頭代表将前面的參數傳遞給後面的代碼;
- 後面的輸出語句即業務邏輯代碼。
有參有傳回值代碼,Comparator接口的使用
下面舉例示範
java.util.Comparator<T>
接口的使用場景代碼,其中的抽象方法定義為:
-
public abstract int compare(T o1, T o2);
當需要對一個對象數組進行排序時,
Arrays.sort
方法需要一個
Comparator
接口執行個體來指定排序的規則。假設有一個
Person
類,含有
String name
和
int age
兩個成員變量:
public class Person {
private String name;
private int age;
// 省略構造器、toString方法與Getter Setter
}
傳統寫法
如果使用傳統的代碼對
Person[]
數組進行排序,寫法如下:
public class Demo05Comparator {
public static void main(String[] args) {
// 本來年齡亂序的對象數組
Person[] array = { new Person("古力娜紮", 19), new Person("迪麗熱巴", 18), new Person("馬爾紮哈", 20) };
// 匿名内部類
Comparator<Person> comp = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
};
Arrays.sort(array, comp); // 第二個參數為排序規則,即Comparator接口執行個體
for (Person person : array) {
System.out.println(person);
}
}
}
這種做法在面向對象的思想中,似乎也是“理所當然”的。其中
Comparator
接口的執行個體(使用了匿名内部類)代表了“按照年齡從小到大”的排序規則。
代碼分析
下面我們來搞清楚上述代碼真正要做什麼事情。
- 為了排序,
方法需要排序規則,即Arrays.sort
接口的執行個體,抽象方法Comparator
是關鍵;compare
- 為了指定
的方法體,不得不需要compare
接口的實作類;Comparator
- 為了省去定義一個
實作類的麻煩,不得不使用匿名内部類;ComparatorImpl
- 必須覆寫重寫抽象
方法,是以方法名稱、方法參數、方法傳回值不得不再寫一遍,且不能寫錯;compare
- 實際上,隻有參數和方法體才是關鍵。
Lambda寫法
public class Demo06ComparatorLambda {
public static void main(String[] args) {
Person[] array = {
new Person("古力娜紮", 19),
new Person("迪麗熱巴", 18),
new Person("馬爾紮哈", 20) };
Arrays.sort(array, (Person a, Person b) -> {
return a.getAge() - b.getAge();
});
for (Person person : array) {
System.out.println(person);
}
}
}
省略格式
省略規則
在Lambda标準格式的基礎上,使用省略寫法的規則為:
- 小括号内參數的類型可以省略;
- 如果小括号内有且僅有一個參,則小括号可以省略;
- 如果大括号内有且僅有一個語句,則無論是否有傳回值,都可以省略大括号、return關鍵字及語句分号。
1.4-Lambda的前提條件(了解)
Lambda的文法非常簡潔,完全沒有面向對象複雜的束縛。但是使用時有幾個問題需要特别注意:
-
使用Lambda必須具有接口,且要求接口中有且僅有一個抽象方法。
無論是JDK内置的
、Runnable
接口還是自定義的接口,隻有當接口中的抽象方法存在且唯一時,才可以使用Lambda。Comparator
-
使用Lambda必須具有接口作為方法參數。
也就是方法的參數或局部變量類型必須為Lambda對應的接口類型,才能使用Lambda作為該接口的執行個體。
有且僅有一個抽象方法的接口,稱為“函數式接口”。
第二章:函數式接口
2.1-概述(了解)
介紹
函數式接口在Java中是指:有且僅有一個抽象方法的接口。
函數式接口,即适用于函數式程式設計場景的接口。而Java中的函數式程式設計展現就是Lambda,是以函數式接口就是可以适用于Lambda使用的接口。隻有確定接口中有且僅有一個抽象方法,Java中的Lambda才能順利地進行推導。
從應用層面來講,Java中的Lambda可以看做是匿名内部類的簡化格式。
格式
隻要確定接口中有且僅有一個抽象方法即可:
修飾符 interface 接口名稱 {
public abstract 傳回值類型 方法名稱(可選參數資訊);
// 其他非抽象方法内容
}
public abstract 可以省略
FunctionalInterface注解
與
@Override
注解的作用類似,Java 8中專門為函數式接口引入了一個新的注解:
@FunctionalInterface
。該注解可用于一個接口的定義上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
一旦使用該注解來定義接口,編譯器将會強制檢查該接口是否确實有且僅有一個抽象方法,否則将會報錯。不過,即使不使用該注解,隻要滿足函數式接口的定義,這仍然是一個函數式接口,使用起來都一樣。
2.2-常用的函數式接口(重點)
JDK提供了大量常用的函數式接口以豐富Lambda的典型使用場景,它們主要在
java.util.function
包中被提供。下面是最簡單的幾個接口及使用示例。
Supplier接口
java.util.function.Supplier<T>
接口,它意味着"供給" , 對應的Lambda表達式需要“對外提供”一個符合泛型類型的對象資料。
抽象方法 : get
僅包含一個無參的方法:
T get()
。用來擷取一個泛型參數指定類型的對象資料。
public class Demo08Supplier {
private static String getString(Supplier<String> function) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() -> msgA + msgB));
}
}
求數組元素最大值
使用
Supplier
接口作為方法參數類型,通過Lambda表達式求出int數組中的最大值。提示:接口的泛型請使用
java.lang.Integer
類。
代碼示例:
public class DemoIntArray {
public static void main(String[] args) {
int[] array = { 10, 20, 100, 30, 40, 50 };
printMax(() -> {
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
});
}
private static void printMax(Supplier<Integer> supplier) {
int max = supplier.get();
System.out.println(max);
}
}
Consumer接口
java.util.function.Consumer<T>
接口則正好相反,它不是生産一個資料,而是消費一個資料,其資料類型由泛型參數決定。
抽象方法:accept
Consumer
接口中包含抽象方法
void accept(T t)
,意為消費一個指定泛型的資料。基本使用如:
import java.util.function.Consumer;
public class Demo09Consumer {
private static void consumeString(Consumer<String> function , String str) {
function.accept(str);
}
public static void main(String[] args) {
consumeString(s -> System.out.println(s));
}
}
Function接口
java.util.function.Function<T,R>
接口用來根據一個類型的資料得到另一個類型的資料,前者稱為前置條件,後者稱為後置條件。有進有出,是以稱為“函數Function”。
抽象方法:apply
Function
接口中最主要的抽象方法為:
R apply(T t)
,根據類型T的參數擷取類型R的結果。使用的場景例如:将
String
類型轉換為
Integer
類型。
public class Demo11FunctionApply {
private static void method(Function<String, Integer> function, Str str) {
int num = function.apply(str);
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s -> Integer.parseInt(s) , "10");
}
}
Predicate接口
有時候我們需要對某種類型的資料進行判斷,進而得到一個boolean值結果。這時可以使用
java.util.function.Predicate<T>
接口。
抽象方法:test
Predicate
接口中包含一個抽象方法:
boolean test(T t)
。用于條件判斷的場景,條件判斷的标準是傳入的Lambda表達式邏輯,隻要字元串長度大于5則認為很長。
public class Demo15PredicateTest {
private static void method(Predicate<String> predicate,String str) {
boolean veryLong = predicate.test(str);
System.out.println("字元串很長嗎:" + veryLong);
}
public static void main(String[] args) {
method(s -> s.length() > 5, "HelloWorld");
}
}
第三章:Stream流
在Java 8中,得益于Lambda所帶來的函數式程式設計,引入了一個全新的Stream概念,用于解決已有集合類庫既有的弊端。
3.1-為什麼要用Stream流(了解)
傳統集合的多步周遊代碼
幾乎所有的集合(如
Collection
接口或
Map
接口等)都支援直接或間接的周遊操作。而當我們需要對集合中的元素進行操作的時候,除了必需的添加、删除、擷取外,最典型的就是集合周遊。例如:
public class Demo10ForEach {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三豐");
for (String name : list) {
System.out.println(name);
}
}
}
這是一段非常簡單的集合周遊操作:對集合中的每一個字元串都進行列印輸出操作。
循環周遊的弊端
Java 8的Lambda讓我們可以更加專注于做什麼(What),而不是怎麼做(How),這點此前已經結合内部類進行了對比說明。現在,我們仔細體會一下上例代碼,可以發現:
- for循環的文法就是“怎麼做”
- for循環的循環體才是“做什麼”
為什麼使用循環?因為要進行周遊。但循環是周遊的唯一方式嗎?周遊是指每一個元素逐一進行處理,而并不是從第一個到最後一個順次處理的循環。前者是目的,後者是方式。
試想一下,如果希望對集合中的元素進行篩選過濾:
- 将集合A根據條件一過濾為子集B;
- 然後再根據條件二過濾為子集C。
那怎麼辦?在Java 8之前的做法可能為:
這段代碼中含有三個循環,每一個作用不同:
- 首先篩選所有姓張的人;
- 然後篩選名字有三個字的人;
- 最後進行對結果進行列印輸出。
public class Demo11NormalFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三豐");
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if (name.startsWith("張")) {
zhangList.add(name);
}
}
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
}
for (String name : shortList) {
System.out.println(name);
}
}
}
每當我們需要對集合中的元素進行操作的時候,總是需要進行循環、循環、再循環。這是理所當然的麼?**不是。**循環是做事情的方式,而不是目的。另一方面,使用線性循環就意味着隻能周遊一次。如果希望再次周遊,隻能再使用另一個循環從頭開始。
那,Lambda的衍生物Stream能給我們帶來怎樣更加優雅的寫法呢?
Stream的更優寫法
下面來看一下借助Java 8的Stream API,什麼才叫優雅:
public class Demo12StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三豐");
list.stream()
.filter(s -> s.startsWith("張"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
}
}
直接閱讀代碼的字面意思即可完美展示無關邏輯方式的語義:擷取流、過濾姓張、過濾長度為3、逐一列印。代碼中并沒有展現使用線性循環或是其他任何算法進行周遊,我們真正要做的事情内容被更好地展現在代碼中。
3.2-流式思想(了解)
注意:請暫時忘記對傳統IO流的固有印象!
整體來看,流式思想類似于工廠工廠中的房間的“生産流水線”。
當需要對多個元素進行操作(特别是多步操作)的時候,考慮到性能及便利性,我們應該首先拼好一個“模型”步驟 方案,然後再按照方案去執行它。
這張圖中展示了過濾、映射、跳過、計數等多步操作,這是一種集合元素的處理方案,而方案就是一種“函數模型”。圖中的每一個方框都是一個“流”,調用指定的方法,可以從一個流模型轉換為另一個流模型。而最右側的數字3是最終結果。
3.3-擷取流的方式(重點)
java.util.stream.Stream<T>
是Java 8新加入的最常用的流接口。(這并不是一個函數式接口。)
擷取一個流非常簡單,有以下幾種常用的方式:
- 所有的
集合都可以通過Collection
預設方法擷取流;stream
-
接口的靜态方法Stream
可以擷取數組對應的流。of
方式1 : 根據Collection擷取流
首先,
java.util.Collection
接口中加入了default方法
stream
用來擷取流,是以其所有實作類均可擷取流。
import java.util.*;
import java.util.stream.Stream;
/*
擷取Stream流的方式
1.Collection中 方法
Stream stream()
2.Stream接口 中靜态方法
of(T...t) 向Stream中添加多個資料
*/
public class Demo13GetStream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// ...
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
// ...
Stream<String> stream2 = set.stream();
}
}
方式2: 根據數組擷取流
如果使用的不是集合或映射而是數組,由于數組對象不可能添加預設方法,是以
Stream
接口中提供了靜态方法
of
,使用很簡單:
import java.util.stream.Stream;
public class Demo14GetStream {
public static void main(String[] args) {
String[] array = { "張無忌", "張翠山", "張三豐", "張一進制" };
Stream<String> stream = Stream.of(array);
}
}
of
方法的參數其實是一個可變參數,是以支援數組。
3.4-常用方法(重點)
流模型的操作很豐富,這裡介紹一些常用的API。這些方法可以被分成兩種:
- 終結方法:傳回值類型不再是
接口自身類型的方法,是以不再支援類似Stream
那樣的鍊式調用。本小節中,終結方法包括StringBuilder
和count
方法。forEach
- 非終結方法:傳回值類型仍然是
接口自身類型的方法,是以支援鍊式調用。(除了終結方法外,其餘方法均為非終結方法。)Stream
更多方法,請自行參考API文檔。
逐一處理方法:forEach
雖然方法名字叫 forEach ,但是與for循環中的“for-each”昵稱不同。
該方法接收一個 Consumer 接口函數,會将每一個流元素交給該函數進行處理。
java.util.function.Consumer<T>接口是一個消費型接口。
Consumer接口中包含抽象方法void accept(T t),意為消費一個指定泛型的資料。
基本使用
public class Test01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張三");
list.add("李四");
list.add("王五");
list.stream().forEach(name-> System.out.println(name));
}
}
過濾方法:filter
可以通過 filter 方法将一個流轉換成另一個子集流。方法簽名:
該接口接收一個 Predicate 函數式接口參數(可以是一個Lambda或方法引用)作為篩選條件。
複習Predicate接口
java.util.stream.Predicate
函數式接口,其中唯一的抽象方法為:
該方法将會産生一個boolean值結果,代表指定的條件是否滿足。如果結果為true,那麼Stream流的 filter 方法 将會留用元素;如果結果為false,那麼 filter 方法将會舍棄元素。
基本使用
Stream流中的 filter 方法基本使用的代碼如:
public class Test02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張三");
list.add("李四");
list.add("張三豐");
list.add("王五");
list.add("張無忌");
list.stream()
.filter(name->name.startsWith("張"))
.forEach(name-> System.out.println(name));
}
}
在這裡通過Lambda表達式來指定了篩選的條件:必須姓張。
映射方法:map
如果需要将流中的元素映射到另一個流中,可以使用
map
方法。方法簽名:
該接口需要一個 Function 函數式接口參數,可以将目前流中的T類型資料轉換為另一種R類型的流。
複習Function接口
此前我們已經學習過
java.util.stream.Function
函數式接口,其中唯一的抽象方法為:
這可以将一種T類型轉換成為R類型,而這種轉換的動作,就稱為“映射”
基本使用
public class Test03 {
public static void main(String[] args) {
String[]strs = {"11","22","33","44"};
Stream.of(strs)
.map((s -> Integer.parseInt(s)))
.forEach(i-> System.out.println(i+2));
}
}
這段代碼中, map 方法的參數通過方法引用,将字元串類型轉換成為了int類型(并自動裝箱為 Integer 類對 象)。
統計個數方法:count
正如舊集合 Collection 當中的 size 方法一樣,流提供 count 方法來數一數其中的元素個數:
該方法傳回一個long值代表元素個數(不再像舊集合那樣是int值)。基本使用:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張三");
list.add("李四");
list.add("張三豐");
list.add("王五");
list.add("張無忌");
long i = list.stream()
.filter(name->name.startsWith("張"))
.count();
System.out.println(i);//3
}
取用前幾個方法:limit
limit 方法可以對流進行截取,隻取用前n個。方法簽名:
參數是一個long型,如果集合目前長度大于參數則進行截取;否則不進行操作。基本使用:
public class Test06 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張三");
list.add("李四");
list.add("張三豐");
list.add("王五");
list.add("張無忌");
list.stream()
.filter(name->name.startsWith("張"))
.limit(2)
.forEach(name-> System.out.println(name));
}
}
// 結果-張三、張三豐
跳過前幾個方法:skip
如果希望跳過前幾個元素,可以使用 skip 方法擷取一個截取之後的新流:
如果流的目前長度大于n,則跳過前n個;否則将會得到一個長度為0的空流。基本使用:
public class Test07 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張三");
list.add("李四");
list.add("張三豐");
list.add("王五");
list.add("張無忌");
list.stream()
.filter(name->name.startsWith("張"))
.skip(2)
.forEach(name-> System.out.println(name));
}
}
// 結果:張無忌
組合方法,concat
如果有兩個流,希望合并成為一個流,那麼可以使用 Stream 接口的靜态方法 concat :
這是一個靜态方法,與 java.lang.String 當中的 concat 方法是不同的。
該方法的基本使用代碼如:
public class Test08 {
public static void main(String[] args) {
Stream<String> s1 = Stream.of("張三","李四");
Stream<String> s2 = Stream.of("王五","趙六");
Stream.concat(s1,s2).forEach(name-> System.out.println(name));
}
}
// 結果:張三、李四、王五、趙六
流轉集合方法,collect
從Stream流對象轉成集合對象,使用
Stream
接口方法
collect:
public class StreamDemo06 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("張無忌");
list.add("周芷若");
list.add("張三豐");
Stream<String> stream = list.stream();
//Stream流對象collect()傳遞Collectors靜态方法toList() 流對象元素轉成集合
List<String> newList = stream.filter( s->s.startsWith("張")).collect(Collectors.toList());
System.out.println(newList);
}
}
3.5-綜合案例(練習)
需求
現在有兩個
ArrayList
集合存儲隊伍當中的多個成員姓名,要求使用傳統的for循環(或增強for循環)依次進行以下若幹操作步驟:
- 第一個隊伍隻要名字為3個字的成員姓名;
- 第一個隊伍篩選之後隻要前3個人;
- 第二個隊伍隻要姓張的成員姓名;
- 第二個隊伍篩選之後不要前2個人;
- 将兩個隊伍合并為一個隊伍;
- 列印整個隊伍的姓名資訊。
兩個隊伍(集合)的代碼如下:
public class Demo21ArrayListNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪麗熱巴");
one.add("宋遠橋");
one.add("蘇星河");
one.add("老子");
one.add("莊子");
one.add("孫子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜紮");
two.add("張無忌");
two.add("張三豐");
two.add("趙麗穎");
two.add("張二狗");
two.add("張天愛");
two.add("張三");
// ....
}
}
傳統方式
使用for循環 , 示例代碼:
public class Demo22ArrayListNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
// ...
List<String> two = new ArrayList<>();
// ...
// 第一個隊伍隻要名字為3個字的成員姓名;
List<String> oneA = new ArrayList<>();
for (String name : one) {
if (name.length() == 3) {
oneA.add(name);
}
}
// 第一個隊伍篩選之後隻要前3個人;
List<String> oneB = new ArrayList<>();
for (int i = 0; i < 3; i++) {
oneB.add(oneA.get(i));
}
// 第二個隊伍隻要姓張的成員姓名;
List<String> twoA = new ArrayList<>();
for (String name : two) {
if (name.startsWith("張")) {
twoA.add(name);
}
}
// 第二個隊伍篩選之後不要前2個人;
List<String> twoB = new ArrayList<>();
for (int i = 2; i < twoA.size(); i++) {
twoB.add(twoA.get(i));
}
// 将兩個隊伍合并為一個隊伍;
List<String> totalNames = new ArrayList<>();
totalNames.addAll(oneB);
totalNames.addAll(twoB);
// 列印整個隊伍的姓名資訊。
for (String name : totalNames) {
System.out.println(name);
}
}
}
運作結果為:
宋遠橋
蘇星河
洪七公
張二狗
張天愛
張三
Stream方式
等效的Stream流式處理代碼為:
public class Demo23StreamNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
// ...
List<String> two = new ArrayList<>();
// ...
// 第一個隊伍隻要名字為3個字的成員姓名;
// 第一個隊伍篩選之後隻要前3個人;
Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
// 第二個隊伍隻要姓張的成員姓名;
// 第二個隊伍篩選之後不要前2個人;
Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("張")).skip(2);
// 将兩個隊伍合并為一個隊伍;
// 根據姓名建立Person對象;
// 列印整個隊伍的Person對象資訊。
Stream.concat(streamOne, streamTwo).forEach(s->System.out.println(s));
}
}
運作效果完全一樣:
宋遠橋
蘇星河
洪七公
張二狗
張天愛
張三