前言
項(xiàng)目代碼在本級(jí)目錄下的lambda_demo中
說(shuō)起 JDK8 的新特性帆疟,總繞不過(guò)兩個(gè),一個(gè)是流(stream)自赔,一個(gè)就是 lambda表達(dá)式绍妨。
因?yàn)?引入 lambda 表達(dá)式 可以說(shuō)是 Java 的一次革命性的嘗試他去,因?yàn)閘ambda表達(dá)式是函數(shù)式編程灾测,把函數(shù)方法參數(shù)化。而之前的 Java 是面向?qū)ο蟮脑始幔浅7浅?yán)格的。甚至可以說(shuō)是呆板鲜结。
但是 Java的之前的那種風(fēng)格 或者說(shuō)設(shè)定并不能說(shuō)是他的弊端精刷,對(duì)于剛?cè)腴T(mén)編程的人或者初中級(jí)的Java編程者來(lái)說(shuō)蔗候,Java的這種語(yǔ)法更是他的優(yōu)勢(shì)锈遥,尤其是企業(yè)級(jí)的應(yīng)用來(lái)說(shuō)所灸,易于維護(hù)和易于理解比運(yùn)行效率更重要。這也是Java最近幾年被作為企業(yè)級(jí)應(yīng)用程序的首選語(yǔ)言的原因钾唬,因?yàn)檎l(shuí)都不能保證和你共事的人都是頂尖的人才抡秆,你寫(xiě)的他都懂儒士,他寫(xiě)的你都懂
但是乍桂,隨著軟件的發(fā)展,會(huì)編程的人越來(lái)越多睹酌,門(mén)檻不斷升高权谁,你不學(xué)習(xí),不進(jìn)步憋沿,那你就只能被退休旺芽。連Java都開(kāi)始改變他的語(yǔ)法了,你都不準(zhǔn)備改變嗎
這篇文章辐啄,我不準(zhǔn)備直接講語(yǔ)法采章,因?yàn)榛久總€(gè)人第一次看到 lambda語(yǔ)法直接就懵的。講了也記不住悯舟,不理解,不會(huì)用砸民,到時(shí)候講了也是白講
我用個(gè)小例子講述下抵怎,我們?cè)谧鯿rud的時(shí)候,都是怎么完成功能的岭参。然后怎么根據(jù)需求變更版本反惕,最后再講lambda表達(dá)式
如果想運(yùn)用lambda表達(dá)式,使用JDK8的另一個(gè)新特性 Stream 演侯,效果會(huì)更好姿染,可以參考我的這篇文章 JDK8 新特性stream
如果圖片加載不出來(lái),可以在github上看秒际,這里的文章更全更新更及時(shí)悬赏。lambda表達(dá)式
背景
假設(shè)我們現(xiàn)在在做一個(gè) 學(xué)生管理系統(tǒng)(類(lèi)似于這種的管理系統(tǒng),我想基本應(yīng)該都做過(guò))程癌。
我們?cè)趯?xiě)service部分舷嗡,根據(jù)需求來(lái)編寫(xiě)相應(yīng)的方法
項(xiàng)目結(jié)構(gòu)
Ps: 沒(méi)有用嚴(yán)格的項(xiàng)目結(jié)構(gòu),但是大致懂就行啦
基本類(lèi)
package com.leosanqing;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @Author: leosanqing
* @Date: 2020/1/17 上午7:54
* @Package: com.leosanqing
* @Description: 學(xué)生類(lèi)
*/
@Data
@AllArgsConstructor
public class Student {
private String name;
private int age;
private String sex;
/**
* 課程
*/
private String subject;
private int height;
private int weight;
private String teacher;
}
list列表
學(xué)生信息分別都是這些
private static List<Student> studentList = new ArrayList<Student>() {
{
add(new Student("張三豐", 20, "男", "體育",
180, 75, "太上老君"));
add(new Student("張無(wú)忌", 18, "男", "語(yǔ)文",
178, 73, "文曲星"));
add(new Student("趙敏", 17, "女", "數(shù)學(xué)",
170, 50, "太白金星"));
add(new Student("金毛獅王", 25, "男", "體育",
176, 80, "太白金星"));
add(new Student("周芷若", 16, "女", "語(yǔ)文",
168, 48, "太上老君"));
add(new Student("張三", 21, "男", "英語(yǔ)",
172, 65, "如來(lái)"));
add(new Student("趙勇", 26, "男", "體育",
188, 80, "太上老君"));
}
};
版本一
我找出所有學(xué)生中的女學(xué)生
這個(gè)應(yīng)該很簡(jiǎn)單,只需要將寫(xiě)個(gè)過(guò)濾條件就行
/**
* 版本一
* 找出女學(xué)生
*
* @return
*/
public static List<Student> getFemaleStudent(List<Student> studentList) {
List<Student> students = new ArrayList<>();
for (Student student : studentList) {
if ("女".equals(student.getSex())) {
students.add(student);
}
}
return students;
}
版本二
上面的太死了嵌莉,只能找出女學(xué)生进萄。我要能根據(jù)輸入的性別來(lái)找。將性別作為參數(shù)傳進(jìn)入
/**
* 版本二
* <p>
* 根據(jù)輸入性別查找學(xué)生
*
* @param studentList
* @param sex
* @return
*/
public static List<Student> getStudentBySex(List<Student> studentList, String sex) {
List<Student> students = new ArrayList<>();
for (Student student : studentList) {
if (sex.equals(student.getSex())) {
students.add(student);
}
}
return students;
}
版本三
你這個(gè)功能太少了锐峭,我要不僅能 根據(jù)輸入的年齡或者老師查找中鼠,也能根據(jù)年齡查找的功能的方法。那我們就多設(shè)置幾個(gè)參數(shù)沿癞,把那些要求都作為參數(shù)傳進(jìn)入
/**
* 版本三
* <p>
* 根據(jù)輸入的年齡或者老師查找援雇。true表示使用年齡,false表示使用老師這個(gè)參數(shù)
*
* @param studentList
* @param age
* @return
*/
public static List<Student> getStudentByAgeOrTeacher(List<Student> studentList, int age,
String teacher, boolean ageOrTeacher) {
List<Student> students = new ArrayList<>();
for (Student student : studentList) {
if ((ageOrTeacher && student.getAge() > age)
|| !ageOrTeacher && teacher.equals(student.getTeacher())
) {
students.add(student);
}
}
return students;
}
版本四
版本三解決了一個(gè)需求椎扬,但是如果我的需求復(fù)雜惫搏,要求多呢具温?我就要輸入n多個(gè)參數(shù)
這個(gè)時(shí)候,我們就要將其抽象一下筐赔,使用面向?qū)ο蟮乃枷胂承伞S靡幌?策略者模式。(嚴(yán)格意義上說(shuō)茴丰,雖然用Java編程达皿,但是很多人根本連Java都沒(méi)有入門(mén),因?yàn)闆](méi)有用過(guò) 面向?qū)ο蟮乃枷牖呒纾救敲嫦蜻^(guò)程 峦椰。你自己定義過(guò) 接口,使用過(guò)多態(tài)嗎汰规?使用過(guò)抽象類(lèi)嗎汤功?使用過(guò)繼承嗎?Spring框架很強(qiáng)大控轿,強(qiáng)大到很多人都變"笨"了,我只需要crud冤竹,其他的大部分框架已經(jīng)幫忙做了,我也不需要怎么設(shè)計(jì)茬射,不用考慮怎么抽出通用的類(lèi)和方法)
我們定義一個(gè)接口
package com.leosanqing.predicate;
import com.leosanqing.bean.Student;
/**
* @Author: leosanqing
* @Date: 2020/1/19 下午11:41
* @Package: com.leosanqing.service
* @Description: 學(xué)生條件接口
*/
public interface StudentPredicate {
boolean filter(Student student);
}
來(lái)兩個(gè)實(shí)現(xiàn)類(lèi)
package com.leosanqing.predicate;
import com.leosanqing.bean.Student;
/**
* @Author: leosanqing
* @Date: 2020/1/19 下午11:49
* @Package: com.leosanqing.predicate
* @Description: 根據(jù)年齡過(guò)濾
*/
public class AgePredicate implements StudentPredicate {
@Override
public boolean filter(Student student) {
return student.getAge() > 20;
}
}
package com.leosanqing.predicate;
import com.leosanqing.bean.Student;
/**
* @Author: leosanqing
* @Date: 2020/1/19 下午11:51
* @Package: com.leosanqing.predicate
* @Description: 根據(jù)老師過(guò)濾
*/
public class TeacherPredicate implements StudentPredicate{
@Override
public boolean filter(Student student) {
return "如來(lái)".equals(student.getTeacher());
}
}
/**
* 版本四
*
* 使用多態(tài)完成,使用策略者模式
* @param studentList
* @param predicate
* @return
*/
public static List<Student> filterStudent(List<Student> studentList, StudentPredicate predicate) {
List<Student> students = new ArrayList<>();
for (Student student : studentList) {
if(predicate.filter(student)){
students.add(student);
}
}
return students;
}
版本五
我們覺(jué)得這個(gè)這個(gè)太麻煩了冒签,我們只想用的時(shí)候再對(duì)其進(jìn)行實(shí)現(xiàn)在抛。因此便有了匿名內(nèi)部類(lèi)
/**
* 版本五,使用匿名內(nèi)部類(lèi)萧恕。 使用Test測(cè)試的時(shí)候就是在使用這個(gè)方法刚梭,我們?cè)谑褂玫臅r(shí)候才去實(shí)現(xiàn)具體的方法
*/
@Test
public void anonymousInnerClass(){
final List<Student> studentList = StudentService.getStudentList();
final List<Student> students = StudentService.filterStudent(studentList, new StudentPredicate() {
@Override
public boolean filter(Student student) {
return "如來(lái)".equals(student.getTeacher());
}
});
System.out.println(JSON.toJSONString(students,true));
}
版本六
匿名內(nèi)部類(lèi)已經(jīng)夠簡(jiǎn)單的了吧?但是我們還想再簡(jiǎn)單點(diǎn)票唆,這個(gè)時(shí)候 lambda就出來(lái)了
/**
* 版本六朴读,使用lambda
*/
@Test
public void lambda(){
final List<Student> studentList = StudentService.getStudentList();
final List<Student> students = StudentService.filterStudent(studentList, student -> "如來(lái)".equals(student.getTeacher()));
System.out.println(JSON.toJSONString(students,true));
}
我們看,lambda表達(dá)式其實(shí)和匿名內(nèi)部類(lèi)結(jié)構(gòu)有點(diǎn)類(lèi)似走趋。
通過(guò)這樣的介紹衅金,現(xiàn)在你應(yīng)該知道 lambda 表達(dá)式 其實(shí)也沒(méi)有那么難理解了吧,你就把他當(dāng)成匿名函數(shù)來(lái)理解會(huì)容易的多
在我上大學(xué)的時(shí)候,那個(gè)時(shí)候還在講Java的GUI簿煌,當(dāng)我們定義按鈕和何種監(jiān)聽(tīng)器的時(shí)候氮唯,為了方便,幾乎都用匿名內(nèi)部類(lèi)來(lái)完成姨伟。
再比如我們使用 多線程的時(shí)候
new Thread(new Runnable() {
@Override
public void run() {
}
});
那我們就可以用lambda寫(xiě)成這樣子的
new Thread(()->{
//這個(gè)里面寫(xiě)的就是run方法里面的內(nèi)容
});
接口
函數(shù)式接口 | 參數(shù)類(lèi)型 | 返回類(lèi)型 | 用途 |
---|---|---|---|
Consumer(消費(fèi)型接口) | T | void | 對(duì)類(lèi)型為T(mén)的對(duì)象應(yīng)用操作惩琉。void accept(T t) |
Supplier(供給型接口) | 無(wú) | T | 返回類(lèi)型為T(mén)的對(duì)象。 T get(); |
Function(函數(shù)型接口) | T | R | 對(duì)類(lèi)型為T(mén)的對(duì)象應(yīng)用操作并返回R類(lèi)型的對(duì)象夺荒。R apply(T t); |
Predicate(斷言型接口) | T | boolean | 確定類(lèi)型為T(mén)的對(duì)象是否滿(mǎn)足約束瞒渠。boolean test(T t); |
上面是我們常用到的四種函數(shù)接口良蒸,我們之前代碼展示的就是 Predicate的接口,我們使用的 filter方法伍玖,返回的是一個(gè) bool值
如果想了解或者實(shí)戰(zhàn)诚啃,使用stream編程的時(shí)候會(huì)更深刻,可以看看我的這篇文章私沮。 Stream流編程
那么如果我們自己定義一個(gè)你能夠使用 lambda表達(dá)式的接口始赎,應(yīng)該注意什么呢
條件
接口中有且只有一個(gè)抽象方法
我們看到我們的代碼中,我們的接口中只有 filter 這一個(gè)抽象方法仔燕。
我們自己定義的時(shí)候造垛,可以使用jdk8 新提供的一個(gè)注解 @FunctionalInterface
,這個(gè)沒(méi)有其他作用晰搀,就是標(biāo)示他是一個(gè)函數(shù)式接口五辽,也就是能夠直接用來(lái)寫(xiě)lambda表達(dá)式的接口。不滿(mǎn)足條件的就會(huì)報(bào)錯(cuò)
比如我們接口中沒(méi)有抽象方法外恕,或者抽象方法有兩個(gè)及以上就會(huì)報(bào)以下的錯(cuò)誤
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-CamnJZPL-1581398448695)(img/Xnip2020-02-11_12-42-34.jpg)]
方法引用
我們有時(shí)候會(huì)看到這樣的代碼
public void peekTest() {
studentList.stream()
.peek(System.out::println)
.sorted(Comparator.comparingInt(Student::getAge))
.forEach(stu -> System.out.println(JSON.toJSONString(stu, true)));
}
第一次看這個(gè)的時(shí)候肯定會(huì)懵杆逗,這個(gè)雙冒號(hào)是什么鬼?
其實(shí)這個(gè)是 方法引用
定義
方法引用 是 lambda表達(dá)式的一種快捷寫(xiě)法鳞疲,記住他是lambda的一種快捷寫(xiě)法罪郊,要先能寫(xiě)成lambda才能寫(xiě)成 方法引用
Stu :: getAge
目標(biāo)引用 雙冒號(hào) 方法名
類(lèi)型
-
指向 靜態(tài)方法的方法引用
public void test1(){ Consumer<String> consumer1 = number -> Integer.parseInt(number); Consumer<String> consumer2 = Integer::parseInt; }
-
指向現(xiàn)有對(duì)象的實(shí)例方法的方法引用
public void test2(){ Consumer<String> consumer1 = number -> number.length(); Consumer<String> consumer2 = String::length; }
-
指向任意類(lèi)型實(shí)例方法的方法引用
public void test3(){ StringBuilder stringBuilder = new StringBuilder(); Consumer<String> consumer1 = number -> stringBuilder.append(number); Consumer<String> consumer2 = stringBuilder::append; }