1.Stream流
1.for循環帶來的弊端
- 在jdk8中,lambda專注於做什么,而不是怎么做
- for循環的語法就是怎么做
- for循環的循環體才是做什么
遍歷是指每一個元素逐一進行處理,而並不是從第一個到最后一個順次處理的循環。前者是目的,后者是方式。
集合存儲案列:
import java.util.ArrayList; import java.util.List; public class Demo{ public static void main(String[] args){ // 創建一個list集合,存儲姓名 List<String> list = new ArrayList<>(); list.add("張無忌"); list.add("周芷若"); list.add("趙敏"); list.add("趙強"); list.add("張三豐"); //對list集合中的元素進行過濾,只要以張開頭的元素,存儲到一個新的集合中 list<String> listA = new ArrayList<>(); for(String str:list){ if(str.startsWidth("張")){ listA.add(str); } } //對listA集合進行過濾,只要姓名長度為3的人,存儲到一個新集合中 List<String> ListB = new ArrayList<>(); for(String s:listA){ if(s.length()==3){ listB.add(s); } } } }
2.使用Stream的更優寫法
import java.util.ArrayList; import java.util.List; public class Demo{ public static void main(String[] args){ // 創建一個list集合,存儲姓名 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(System.out::println); } }
流思想
Stream(流)是一個來自數據源的元素隊列
- 元素是特定類型的對象,形成一個隊列。Java中的Stream並不會存儲元素,二十按需計算。
- 數據源流的來源,可以是 集合, 數組等。
和以前的Collection操作不同,Stream操作還有兩個基礎的特征:
-
Pipelining:中間操作都會返回流對象本省。着多個操作可以串聯一個管道,如同流式風格,可以對操作進行優化, 比如延遲執行(laziness)和短路(short-circuiting)。
-
內部迭代:以前對集合變量都是通過Iterator或者是增強for循環的方式,顯示在集合外部進行迭代,這種就是外部迭代。Stream提供了內部迭代的方式,流可以直接調用遍歷方法。
-
使用流的步驟
獲取一個數據源(source)→ 數據轉換→執行操作獲取想要的結 果,每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以 像鏈條一樣排列,變成一個管道。
3.獲取流
獲取一個流的方法非常簡單,有以下幾種常用的方式:
- 所有的Collection集合都可以通過Stream默認方法獲取流;
- default Stream<E> stream()
- Stream接口的靜態方法of可以獲取數據對應的流。
- static <T> Stream<T> of(T.. values)
- 參數是一個可變參數,那么可以傳遞一個數組
public class Demo{ public static void main(String[] args){ // 把集合轉為Stream流 List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream(); Set<String> set = new HashSet<>(); Stream<String> stream2 = set.stream(); Map<String,String> map = new HashMap<>(); //獲取鍵,春初到Set集合中 Set<String> KeySet = map.KeySet(); Stream<String> stream3 = KeySet.stream(); //獲取值,存儲帶一個Collection集合中 Collection<String> values = map.values(); Stream<String> stream4 = values.stream(); //獲取鍵值對(鍵與值的映射關系 entrySet) Set<Map.Entry<String, String>> entries = map.entrySet(); Stream<Map.Entry<String, String>> stream5 = entries.stream(); //把數組轉換為Stream流 Stream<Integer> stream6 = Stream.of(1,2,3,4,5,6); //可變參數傳遞數組 Integer[] arr = {1,2,3,4,5,6}; Stream<Integer> stream7 = Stream.of(arr); Strng[] arr2 = {"a", "bn", "cd"}; Stream<String> stream8 = Stream.of(arr2); } }
4.常用方法
forEach方法
Stream流常用方法 forEach
- void forEach(Consumer<? super T> action):
- 該方法節后一個Consumer接口函數,會將每一個流元素交給該函數進行處理。
- Consumer接口是一個消費型的函數式接口, 可以傳遞Lambda表達式,消費數據
簡單記:
- forEach方法,用來遍歷流中的數據
- 是一個終結方法,遍歷之后就不能繼續調用Stream流中的其他方法
public class Demo{ public class void main(String[] args){ // 獲取一個Stream流 Stream<String> stream = Stream.of("張三","李四","王五","趙六","田七"); // 使用stream流中的方法forEach對Stream流中的數據進行遍歷 /* stream.forEach((String name)->{ System.out.println(name); });*/ stream.forEach(name->system.out.println(name)); } }
filter方法
用於對Stream流中的數據進行過濾
- Stream<T> filter(Predicate<? super T> predicate);
- filter方法中的參數Predicate是一個函數式接口,所以可以傳遞Lambda表達式,對數據進行過濾
- Predicate中的抽象方法:
- boolean test(T t)
public class Demo{ public static void main(String[] args){ // 創建一個Stream流 Stream<String> stream = Stream.of("張三豐", "張翠山", "趙敏", "周芷若", "張無忌"); // 對Stream中的元素進行過濾,只要姓張的人 Stream<String> stream2 = stream.filter((String name)->{return name.stratsWith("張");}); // 遍歷Stream2 stream2.forEach(name->System.out.println(name)); /* Stream流屬於管道流,只能被消費(使用)一次 第一個Stream流調用完畢方法,數據就會流轉到下一個Stream上 而這時第一個Stream流已經使用完畢,就會關閉了 所以第一個Stream流就不能再調用方法了 IllegalStateException: stream has already been operated upon or closed */ // 遍歷stream流 stream.forEach(name-> System.out.println(name)); } }
map方法
用於類型轉換
- 如果需要將流中的元素映射到另一個流中,可以使用map方法
- <R> Stream<R> map(Function <? super T, ? extends R> mapper);
- 該接口需要一個Function函數式接口參數,可以將當前流中的T類型轉換為另一種R數據的流
- Function的抽象方法
- R apply(T t);
public class Demo{ public static void main(String[] args){ // 獲取一個String類型的Stream流 Stream<String> stream = Stream.of("1", "2", "3", "4"); // 使用map方法,把字符串類型的整數,轉換為Integer類型的整數 Stream<Integer> stream2 = stream.map((String s)-> {return Integer.parseInt(s);}); // 變量stream2 stream2.forEach(i -> System.out.println(i)); } }
count 方法
用於統計Stream流中元素的個數
- long count();
- count 方法是一個終結方法,返回值是一個long類型的整數,只能使用一次,使用完之后,不可以使用流的其他方法
public class Demo{ public static void main(String[] args){ //獲取一個Stream流 ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); Stream<Integer> Stream = list.stream(); long count = stream.count(); System.out.println(count); // 4 } }
limit方法
用於截取流中的元素
- limit方法可以對流進行截取,支取前n個
- Stream<T> limit(long maxSize);
- 參數是一個int類型。如果當前的長度大於參數則進行截取, 否則不進行截取
- limit方法是一個延遲方法,只是對流中的元素進行截取,返回的是一個新的流,所以可以繼續調用Stream流中的其他方法
public class Demo{ public static void main(String[] args){ // 獲取一個Stream流 String[] arr = {"美羊羊","喜洋洋","懶洋洋","灰太狼","紅太狼"}; Stream<String> stream = Stream.of(arr); // 使用limit放啊進行截取,只要前三個元素 Stream<String> stream2 = stream.limit(3); // 遍歷操作 stream2.forEach(name->System.out.println(name)); } }
skip方法
用於跳過元素,截取后面的元素
- 如果希望跳過前幾個元素,可以使用skip方法獲取一個截取之后的新流
- Stream<T> skip(long n);
- 如果流的當前長度大於n,則跳過錢n個, 否則得到一個長度為0的空流.
public class Demo{ public static void main(String[] args){ // 獲取一個Stream流 String[] arr = {"美羊羊","喜洋洋","懶洋洋","灰太狼","紅太狼"}; Stream<String> stream = Stream.of(arr); // 使用skip方法跳過前面三個元素 Stream<String> stream2 = stream.skip(3); // 遍歷stream2 stream2.forEach(name-> System.out.println(name)); } }
concat方法
用於把流組合在一起
- 如果有兩個流,希望合並成一個流,那么可以使用Stream接口的靜態方法concat
- static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T>b)
public class Demo{ public static void main(String[] args){ //創建一個Stream流 Stream<String> stream1 = Stream.of("張三豐", "張翠山", "趙敏", "周芷若", "張無忌"); // 獲取一個Stream流 String[] arr = {"美羊羊","喜洋洋","懶洋洋","灰太狼","紅太狼"}; Stream<String> stream2 = Stream.of(arr); // 把以上兩個流組成為一個流 Stream<String> concat = Stream.concat(stream1, stream2); concat.forEach(name->System.out.println(name)); } }
2.方法引用
1.冗余的Lambda
定義一個打印的函數式接口
@FunctionalInterface public interface Printable { //定義字符串的抽象方法 void print(String s); }
打印字符串
public class Demo{ // 定義一個方法,參數傳遞的是Printable的接口,對字符串進行打印 public static void printString(Printable p){ p.print("hello world"); } public static void main(String[] args){ //調用printString方法,方法的參數Printable是一個函數式的接口,所以可以傳遞lambda函數 printString((s)->{ System.out.println(s); }); /* 分析: Lambda表達式的目的,打印參數傳遞的字符串 把參數s,傳遞給了System.out對象,調用out對象中的方法println對字符串進行了輸出 注意: 1.System.out對象是已經存在的 2.println方法也是已經存在的 所以我們可以使用方法引用來優化Lambda表達式 可以使用System.out方法直接引用(調用)println方法 */ printString(System.out::println); } }
2.分析
這段代碼的問題在於,對字符串進行控制台打印輸出的操作方案,明明已經有了現成的實現,那就是 System.out 對象中的 println(String) 方法。既然Lambda希望做的事情就是調用 println(String) 方法,那何必自己手動調 用呢?
3.使用方法引用
public class Demo{ public static void printString(Printable p){ p.print("Hello world"); } public static void main(String[] args){ // 調用方法使用方法的引用 printString(System.out::println); } }
4.方法引用符
雙冒號:: 為引用運算符,稱為方法引用。如果Lambda要表達的函數方案已經存在於某個方法的實現中,name我們可以使用雙冒號來引用該方法作為lambda的替代者。
語法分析
例如上例中, System.out 對象中有一個重載的 println(String) 方法恰好就是我們所需要的。那么對於
printString 方法的函數式接口參數,對比下面兩種寫法,完全等效:
- Lambda表達式寫法: s -> System.out.println(s);
- 方法引用寫法: System.out::println
第一種語義是指:拿到參數之后經Lambda之手,繼而傳遞給 System.out.println 方法去處理。
第二種等效寫法的語義是指:直接讓 System.out 中的 println 方法來取代Lambda。兩種寫法的執行效果完全一
樣,而第二種方法引用的寫法復用了已有方案,更加簡潔。
注:Lambda 中 傳遞的參數 一定是方法引用中 的那個方法可以接收的類型,否則會拋出異常
5.通過對象名引用成員方法
定義類
public class MethodRefObject{ public void printUpperCase(String str){ System.out.println(str.toUpperCase()); } }
函數式接口定義
@FunctionalInterface public interface Printable{ void print(String str); }
那么當需要使用這個printUpperCase成員方法來代替Printable接口的Lambda的時候,已經具有了MethodRefObject類的對象實例。則可以通過對象名引用成員方法,代碼:
/* 通過對象名引用成員方法 使用前提: 對象名是已經存在的,成員方法也存在 */ public class Demo{ // 定義一個方法,方法的參數傳遞Printable接口 public static void printString(Ptrintable p){ p.print("hello"); } public static void main(String[] args){ // 創建MethodRerObject對象 MethodRerObject obj = new MethodRerObject(); printString(obj::printUpperCaseString); } }
6.通過類名稱引用靜態方法
由於在java.lang.Math類中已經存在存在了靜態方法abs,所以當我們需要通過Lambda來調用該方法時,有兩種寫法。
函數式接口:
@FunctionalInterface public interface Calcable{ int calc(int num); } public class Demo{ public static int method(int number, Calcable c){ return c.calsAbs(number); } public static void main(String[] args){ // 調用method方法,傳遞計算絕對值 int number = method(-10, Math::abs); System.out.println(number2); } }
7.通過super引用成員方法
定義見面的函數式接口
@FunctionalInterface public interface Greetable{ //定義一個見面的方法 void greet(); }
定義父類
public class Human{ //定義一個sayHello的方法 public void sayHello(){ System.out.println("hello,我是Human"); } }
定義子類
public class Man extends Human{ //子類重寫父類的sayHello方法 @Override public void sayHello(){ System.out.println("hello 我是Man"); } //定義一個方法參數傳遞Greetable接口 public void method(Greetable g){ g.greet(); } public void show(){ method(super::sayHello); } public static void main(String[] args){ new Man().show(); } } // hello 我是Human
8.通過this引用成員方法
定義購買的函數式接口
@FunctionalInterface public interface Richable{ // 定義一個想買什么就買什么的方法 void buy(); }
定義一個類
使用this引用本類的成員方法
public class Husband{ // 定義一個買房子的方法 public void buyHouse(){ System.out.println("北京二環買一套四合院"); } // 定義一個結婚的方法參數傳遞Richable接口 public void marry(Richable r){ r.buy(); } //定義一個非常高興的方法 public void soHappy(){ marry(this::buyHouse); } public static void main(String[] args){ new Husband().soHappy(); } }
9.類的構造器引用
由於構造器的名稱與類名完全一樣,並不固定。所以構造器引用使用 類名稱::new 的格式表示
定義Person類
public class Person{ private String name; public Person(String name){ this.name=name; } public String getName(){ return name; } public void setName(String name){ this.name=name; } }
Person對象的函數式接口
@FunctionalInterface public interface PersonBuilder{ Person builderPerson(String name); }
Demo
public class Demo{ //定義一個方法,傳遞的是姓名和PersonBuilder接口,方法中通過姓名創建Person對象 public static void printName(String name, PersonBuilder pb){ Person person = pb.builderPersom(name); System.out.println(person.getName()); } public static void main(String[] args){ /*構造方法new Person(String name) 已知 創建對象已知 new 就可以使用Person引用new創建對象*/ //使用Person類的帶參構造方法,通過傳遞的姓名創建對象 printName("古力娜扎", Person::new); } }
10.數組的構造器引用
數組也是 Object 的子類對象,所以同樣具有構造器,只是語法稍有不同。
定義一個創建數組的函數式接口
@FunctionalInterface
public class ArrayBuilder{ //定義一個創建int類型數組的方法,參數傳遞的是數組的長度,返回創建的int類型數組 int[] builderArray(int length); }
Demo
public class Demo{ /* 定義一個方法 方法的參數傳遞創建數組的長度和ArrayBuilder接口 方法內部根據傳遞的長度使用ArrayBuilder中的方法創建數組並返回 */ public static int[] createArray(int length, ArrayBuilder ab){ return ab.builderArray(length); } public static void main(String[] args){ /* 使用方法引用優化Lambda表達式 已知創建的就是int[]數組 數組的長度也是已知的 就可以使用方法引用 int[]引用new,根據參數傳遞的長度來創建數組 */ int[] array = createArray(10, int[]::new); System.out.println(Arrays.toString(array)); System.out.println(array.length); } }