所有的檔案和源代碼都開源在GitHub: https://github.com/kun213/JavaNotes上了,希望我們可以一起加油,一起學習,一起交流
day13 JDK8新特性【Lambda、函式式介面、Stream流】
@
目錄- day13 JDK8新特性【Lambda、函式式介面、Stream流】
- 一、Lambda運算式
- 1.1 理解函式式編程相對于面向物件的優點
- 1.2 掌握Lambda運算式的標準格式
- 1.3 掌握Lambda運算式的省略格式與規則
- 1.3.1 省略規則
- 1.3.2 Lambda的前提條件
- 二、函式式介面
- 2.1 如何使用Consumer
函式式介面 - 2.2 如何使用Predicate
函式式介面
- 2.1 如何使用Consumer
- 三、Stream流
- 3.1 掌握常用的流操作
- 3.1.1 獲取流方式
- 3.1.2 常用方法
- forEach : 逐一處理
- filter:過濾
- count:統計個數
- limit:取用前幾個,skip:跳過前幾個
- concat:組合
- collect:流轉集合
- 3.1 掌握常用的流操作
一、Lambda運算式
1.1 理解函式式編程相對于面向物件的優點
在數學中,函式就是有輸入量、輸出量的一套計算方案,也就是“拿什么東西做什么事情”,相對而言,面向物件過分強調“必須通過物件的形式來做事情”,而函式式思想則盡量忽略面向物件的復雜語法——強調做什么,而不是以什么形式做,
1.2 掌握Lambda運算式的標準格式
標準格式:
Lambda省去面向物件的條條框框,格式由3個部分組成:
- 一些引數
- 一個箭頭
- 一段代碼
Lambda運算式的標準格式為:
(引數型別 引數名稱) -> { 代碼陳述句 }
格式說明:
- 小括號內的語法與傳統方法引數串列一致:無引數則留空;多個引數則用逗號分隔,
->是新引入的語法格式,代表指向動作,- 大括號內的語法與傳統方法體要求基本一致,
匿名內部類與lambda對比:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多執行緒任務執行!");
}
}).start();
仔細分析該代碼中,Runnable介面只有一個run方法的定義:
public abstract void run();
即制定了一種做事情的方案(其實就是一個方法):
- 無引數:不需要任何條件即可執行該方案,
- 無回傳值:該方案不產生任何結果,
- 代碼塊(方法體):該方案的具體執行步驟,
同樣的語意體現在Lambda語法中,要更加簡單:
() -> System.out.println("多執行緒任務執行!")
- 前面的一對小括號即
run方法的引數(無),代表不需要任何條件; - 中間的一個箭頭代表將前面的引數傳遞給后面的代碼;
- 后面的輸出陳述句即業務邏輯代碼,
引數和回傳值:
下面舉例演示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);
}
}
}
1.3 掌握Lambda運算式的省略格式與規則
1.3.1 省略規則
在Lambda標準格式的基礎上,使用省略寫法的規則為:
- 小括號內引數的型別可以省略;
- 如果小括號內有且僅有一個參,則小括號可以省略;
- 如果大括號內有且僅有一個陳述句,則無論是否有回傳值,都可以省略大括號、return關鍵字及陳述句分號,
備注:掌握這些省略規則后,請對應地回顧本章開頭的多執行緒案例,
可推導即可省略
Lambda強調的是“做什么”而不是“怎么做”,所以凡是可以推導得知的資訊,都可以省略,例如上例還可以使用Lambda的省略寫法:
Runnable介面簡化:
1. () -> System.out.println("多執行緒任務執行!")
Comparator介面簡化:
2. Arrays.sort(array, (a, b) -> a.getAge() - b.getAge());
1.3.2 Lambda的前提條件
Lambda的語法非常簡潔,完全沒有面向物件復雜的束縛,但是使用時有幾個問題需要特別注意:
- 使用Lambda必須具有介面,且要求介面中有且僅有一個抽象方法,
無論是JDK內置的Runnable、Comparator介面還是自定義的介面,只有當介面中的抽象方法存在且唯一時,才可以使用Lambda, - 使用Lambda必須具有介面作為方法引數,
也就是方法的引數或區域變數型別必須為Lambda對應的介面型別,才能使用Lambda作為該介面的實體,
備注:有且僅有一個抽象方法的介面,稱為“函式式介面”,
二、函式式介面
2.1 如何使用Consumer<T>函式式介面
java.util.function.Consumer<T>介面則正好相反,它不是生產一個資料,而是消費一個資料,其資料型別由泛型引數決定,
抽象方法:accept
Consumer介面中包含抽象方法void accept(T t),意為消費一個指定泛型的資料,基本使用如:
import java.util.function.Consumer;
/**
* 函式式介面 java.util.function.Consumer<T> 消費
* 介面的抽象方法 void accept(T t)
* 消費:
* Consumer<String>
* void accept(String t)
* 輸出長度,切割,截取
*/
public class ConsumerDemo {
public static void main(String[] args) {
// System.out.println("正常呼叫------");
/* MyConsumer mc = new MyConsumer();
acceptString(mc,"你好");*/
/* System.out.println("匿名內部類------");
acceptString(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
},"你好");
}*/
/**
* lambda改進實作類
* 帶引數
* s 引數傳遞到方法中,方法accept()方法,介面重寫方法
* 介面方法accept,lambda中 {}
*/
System.out.println("Lambda------");
acceptString((String s)->{System.out.println(s);},"你好呀");
}
public static void acceptString(Consumer<String> consumer,String str){
consumer.accept(str);
}
}
/*class MyConsumer implements Consumer<String>{
@Override
public void accept(String s) {
System.out.println(s);
}
}*/
2.2 如何使用Predicate<T>函式式介面
有時候我們需要對某種型別的資料進行判斷,從而得到一個boolean值結果,這時可以使用java.util.function.Predicate<T>介面,
抽象方法:test
Predicate介面中包含一個抽象方法:boolean test(T t),用于條件判斷的場景,條件判斷的標準是傳入的Lambda運算式邏輯,只要字串長度大于5則認為很長,
import java.util.function.Predicate;
/**
* java.util.function.Predicate 介面
* 抽象方法:
* Predicate<String>
* boolean test(String t);
*/
public class PredicateDemo {
public static void main(String[] args) {
/* System.out.println("方法呼叫-----------");
boolean b = getBoolean(new MyPredicate(),"464654");
System.out.println(b);
}*/
/*System.out.println("匿名內部類------------");
boolean b = getBoolean(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length()>5;
}
},"464646");
System.out.println(b);
}*/
//lambda運算式,判斷字串長度是否大于5,回傳true
System.out.println("Lambda----------");
boolean b = getBoolean( s->s.length()>5,"46461231");
System.out.println(b);
}
public static boolean getBoolean(Predicate<String> predicate,String s){
return predicate.test(s);
}
}
/*class MyPredicate implements Predicate<String>{
@Override
public boolean test(String s) {
return s.length()<5;
}
}*/
三、Stream流
3.1 掌握常用的流操作
3.1.1 獲取流方式
java.util.stream.Stream<T>是Java 8新加入的最常用的流介面,(這并不是一個函式式介面,)
獲取一個流非常簡單,有以下幾種常用的方式:
- 所有的
Collection集合都可以通過stream默認方法獲取流; Stream介面的靜態方法of可以獲取陣列對應的流,
/**
* java.util.stream.Stream 流物件的介面(流水線)
* 獲取到實作類
*
* 集合:
* Collection介面,JDK8定義方法 Stream介面型別 stream()
*
* 陣列:
* Arrays靜態方法 Stream stream()
*
* 介面Stream定義靜態方法 of(T... values)
*
*/
public class StreamDemo01 {
public static void main(String[] args) {
//根據Collection獲取流
List<String> list = new ArrayList<String>();
list.add("CodeBull");
list.add("你好");
Stream<String> stream = list.stream();
stream.forEach(s-> System.out.println(s));
//根據陣列獲取流
String[] array = {"76","646","5","4654"};
Stream<String> stringStream = Stream.of(array);
stringStream.forEach(s-> System.out.println(s));
}
}
3.1.2 常用方法
流模型的操作很豐富,這里介紹一些常用的API,這些方法可以被分成兩種:
- 終結方法:回傳值型別不再是
Stream介面自身型別的方法,因此不再支持類似StringBuilder那樣的鏈式呼叫,本小節中,終結方法包括count和forEach方法, - 非終結方法:回傳值型別仍然是
Stream介面自身型別的方法,因此支持鏈式呼叫,(除了終結方法外,其余方法均為非終結方法,)
備注:本小節之外的更多方法,請自行參考API檔案,
forEach : 逐一處理
雖然方法名字叫forEach,但是與for回圈中的“for-each”昵稱不同
void forEach(Consumer<? super T> action);
import java.util.stream.Stream;
/**
* Stream介面中的方法:
* foreach()集合中的元素一一進行了操作
* 函式式介面 Consumer 是方法foreach的引數
* 傳遞此介面實作類
*
* 函式式介面 Consumer 消費
* 抽象方法accept
*/
public class StreamDemo02 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("張三豐");
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
//集合方法,獲取Stream流物件
Stream<String> stream = list.stream();
// void accept(String t);
//stream操作的集合泛型是String
//遍歷名s,代表了集合中的元素,s傳遞到accept方法體
stream.forEach(s-> System.out.println(s));
/*list.stream().forEach((String s)->{
System.out.println(s);
});*/
}
在這里,lambda運算式(String str)->{System.out.println(str);}就是一個Consumer函式式介面的示例,
filter:過濾
可以通過filter方法將一個流轉換成另一個子集流,方法宣告:
Stream<T> filter(Predicate<? super T> predicate);
該介面接收一個Predicate函式式介面引數(可以是一個Lambda)作為篩選條件,
基本使用
Stream流中的filter方法基本使用的代碼如:
/**
* Stream物件的方法 filter 過濾
* 過濾掉不需要的集合元素
*
* 過濾:只要姓張的
*
* filter方法的引數,是函式式介面 Predicate (判斷)方法test
* 方法回傳true,不過濾元素,要
*
* 配合: 實作過濾,看不到過濾后的結果,流中的元素逐一操作,方法foreach
*/
public class StreamDemo03 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("周芷諾");
list.add("張無忌");
list.add("張三豐");
//Stream流物件的方法 filter
//s表示集合中的每個元素,傳遞到方法test中
list.stream()
.filter(s -> s.startsWith("張") )
.forEach(s -> System.out.println(s));
}
在這里通過Lambda運算式來指定了篩選的條件:必須姓張,
count:統計個數
正如舊集合Collection當中的size方法一樣,流提供count方法來數一數其中的元素個數:
long count();
該方法回傳一個long值代表元素個數(不再像舊集合那樣是int值),基本使用:
/**
* Stream介面方法count()
* 回傳long,回傳Stream物件中元素的個數
*
* 此流終結,使用完畢,不能在繼續使用Stream物件的方法
* void foreach()終結此流
*/
public class StreamDemo07 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("3", "5", "1", "2");
long count = stream.count();
System.out.println(count);
}
limit:取用前幾個,skip:跳過前幾個
limit方法可以對流進行截取,只取用前n個,skip方法跳過前幾個元素,取用跳過后的元素
//引數是一個long型,如果集合當前長度大于引數則進行截取;否則不進行操作,
Stream<T> limit(long maxSize):獲取Stream流物件中的前n個元素,回傳一個新的Stream流物件
//如果流的當前長度大于n,則跳過前n個;否則將會得到一個長度為0的空流,
Stream<T> skip(long n): 跳過Stream流物件中的前n個元素,回傳一個新的Stream流物件
代碼實作:
import java.util.stream.Stream;
/**
* Stream介面方法
* limit(long n) 取出前幾個元素
* skip(long n) 跳過前幾個元素
*/
public class StreamDemo04 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
//獲取流物件
Stream<Integer> stream = list.stream();
//取出前4個元素,跳過前2個
stream.limit(3).skip(2).forEach(s-> System.out.println(s));
}
concat:組合
如果有兩個流,希望合并成為一個流,那么可以使用Stream介面的靜態方法concat:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): 把引數串列中的兩個Stream流物件a和b,合并成一個新的Stream流物件
備注:這是一個靜態方法,與
java.lang.String當中的concat方法是不同的,
該方法的基本使用代碼如:
import java.util.stream.Stream;
public class Demo20StreamConcat {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("張無忌");
Stream<String> streamB = Stream.of("張翠山");
Stream<String> result = Stream.concat(streamA, streamB);
}
}
collect:流轉集合
從Stream流物件轉成集合物件,使用Stream介面方法collect:
public static void main(String[] args) {
Stream stream = Stream.of("aa","bb","cc","dd","ee","ee");
Set<String> list = ( Set<String> )stream.collect(Collectors.toSet());
for (String s :list){
System.out.println(s);
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/172570.html
標籤:其他
