天天看點

JavaSE第22篇:Lambda表達式、函數式接口

核心概述:在開發中,我們經常使用匿名内部類作為實參傳遞參數,我們可以發現匿名内部類的格式比較繁瑣,那麼如何簡化呢?本篇我們将會學習到Lambda表達式來幫助我們解決問題。另外我們也将學習與Lambda表達式相關的函數式接口,以及Stream流。

JavaSE第22篇:Lambda表達式、函數式接口

第一章:Lambda表達式

1.1-函數式程式設計介紹(了解)

JavaSE第22篇:Lambda表達式、函數式接口

在數學中,函數就是有輸入量、輸出量的一套計算方案,也就是“拿什麼東西做什麼事情”。相對而言,面向對象過分強調“必須通過對象的形式來做事情”,而函數式思想則盡量忽略面向對象的複雜文法——強調做什麼,而不是以什麼形式做。

面向對象的思想: 做一件事情,找一個能解決這個事情的對象,調用對象的方法,完成事情。

函數式程式設計思想: 隻要能擷取到結果,誰去做的,怎麼做的都不重要,重視的是結果,不重視過程 。

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 接口的匿名内部類對象來指定任務内 容,再将其交給一個線程來啟動。

傳統寫法分析:

  1. Thread 類需要 Runnable 接口作為參數,其中的抽象 run 方法是用來指定線程任務内容的核心;
  2. 為了指定 run 的方法體,不得不需要 Runnable 接口的實作類;
  3. 為了省去定義一個 RunnableImpl 實作類的麻煩,不得不使用匿名内部類;
  4. 必須覆寫重寫抽象 run 方法,是以方法名稱、方法參數、方法傳回值不得不再寫一遍,且不能寫錯;
  5. 而實際上,似乎隻有方法體才是關鍵所在。
程式設計思想的轉變

我們真的希望建立一個匿名内部類對象嗎?不。我們隻是為了做這件事情而不得不建立一個對象。我們真正希望做的事情是:将 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個部分組成:
    1. 一些參數
    2. 一個箭頭
    3. 一段代碼
  • 标準格式:

    (參數類型 參數名稱) ‐> { 代碼語句 }

  • 格式說明:
    1. 小括号内的文法與傳統方法參數清單一緻:無參數則留白;多個參數則用逗号分隔。
    2. ->

      是新引入的文法格式,代表指向動作。
    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标準格式的基礎上,使用省略寫法的規則為:

  1. 小括号内參數的類型可以省略;
  2. 如果小括号内有且僅有一個參,則小括号可以省略;
  3. 如果大括号内有且僅有一個語句,則無論是否有傳回值,都可以省略大括号、return關鍵字及語句分号。

1.4-Lambda的前提條件(了解)

Lambda的文法非常簡潔,完全沒有面向對象複雜的束縛。但是使用時有幾個問題需要特别注意:

  1. 使用Lambda必須具有接口,且要求接口中有且僅有一個抽象方法。

    無論是JDK内置的

    Runnable

    Comparator

    接口還是自定義的接口,隻有當接口中的抽象方法存在且唯一時,才可以使用Lambda。
  2. 使用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循環的循環體才是“做什麼”

為什麼使用循環?因為要進行周遊。但循環是周遊的唯一方式嗎?周遊是指每一個元素逐一進行處理,而并不是從第一個到最後一個順次處理的循環。前者是目的,後者是方式。

試想一下,如果希望對集合中的元素進行篩選過濾:

  1. 将集合A根據條件一過濾為子集B;
  2. 然後再根據條件二過濾為子集C。

那怎麼辦?在Java 8之前的做法可能為:

這段代碼中含有三個循環,每一個作用不同:

  1. 首先篩選所有姓張的人;
  2. 然後篩選名字有三個字的人;
  3. 最後進行對結果進行列印輸出。
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流的固有印象!

整體來看,流式思想類似于工廠工廠中的房間的“生産流水線”。

JavaSE第22篇:Lambda表達式、函數式接口

當需要對多個元素進行操作(特别是多步操作)的時候,考慮到性能及便利性,我們應該首先拼好一個“模型”步驟 方案,然後再按照方案去執行它。

JavaSE第22篇:Lambda表達式、函數式接口

這張圖中展示了過濾、映射、跳過、計數等多步操作,這是一種集合元素的處理方案,而方案就是一種“函數模型”。圖中的每一個方框都是一個“流”,調用指定的方法,可以從一個流模型轉換為另一個流模型。而最右側的數字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
JavaSE第22篇:Lambda表達式、函數式接口

可以通過 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
JavaSE第22篇:Lambda表達式、函數式接口

如果需要将流中的元素映射到另一個流中,可以使用

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
JavaSE第22篇:Lambda表達式、函數式接口

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
JavaSE第22篇:Lambda表達式、函數式接口

如果希望跳過前幾個元素,可以使用 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循環)依次進行以下若幹操作步驟:

  1. 第一個隊伍隻要名字為3個字的成員姓名;
  2. 第一個隊伍篩選之後隻要前3個人;
  3. 第二個隊伍隻要姓張的成員姓名;
  4. 第二個隊伍篩選之後不要前2個人;
  5. 将兩個隊伍合并為一個隊伍;
  6. 列印整個隊伍的姓名資訊。

兩個隊伍(集合)的代碼如下:

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));
    }
}
           

運作效果完全一樣:

宋遠橋
蘇星河
洪七公
張二狗
張天愛
張三
           

3.6-函數拼接與終結方法(了解)

JavaSE第22篇:Lambda表達式、函數式接口