先販賣一下焦慮,Java8發於2014年3月18日,距離如今已經快6年了,若是你對Java8的新特性尚未應用,甚至還一無所知,那你真得關注公衆號「程序新視界」,好好系列的學習一下Java8的新特性。Lambda表達式已經在新框架中普通使用了,若是你對Lambda還一無所知,真得認真學習一下本篇文章了。java
如今進入正題Java8的Lambda,首先看一下發音 ([ˈlæmdə])表達式。注意該詞的發音,b是不發音的,da發[də]音。web
爲何要引入Lambda表達式
簡單的來講,引入Lambda就是爲了簡化代碼,容許把函數做爲一個方法的參數傳遞進方法中。若是有JavaScript的編程經驗,立刻會想到這不就是閉包嗎。是的,Lambda表達式也能夠稱做Java中的閉包。spring
先回顧一下Java8之前,若是想把某個接口的實現類做爲參數傳遞給一個方法會怎麼作?要麼建立一個類實現該接口,而後new出一個對象,在調用方法時傳遞進去,要麼使用匿名類,能夠精簡一些代碼。以建立一個線程並打印一行日誌爲例,使用匿名函數寫法以下:編程
new Thread(new Runnable() { @Override public void run() { System.out.println("歡迎關注公衆號:程序新視界"); } }).start();
在java8之前,使用匿名函數已經算是很簡潔的寫法了,再來看看使用Lambda表達式,上面的代碼會變成什麼樣子。微信
new Thread(() -> System.out.println("歡迎關注公衆號:程序新視界")).start();
是否是簡潔到爆!閉包
咱們都知道java是面向對象的編程語言,除了部分簡單數據類型,萬物皆對象。所以,在Java中定義函數或方法都離不開對象,也就意味着很難直接將方法或函數像參數同樣傳遞,而Java8中的Lambda表達式的出現解決了這個問題。app
Lambda表達式使得Java擁有了函數式編程的能力,但在Java中Lambda表達式是對象,它必須依附於一類特別的對象類型——函數式接口(functional interface),後面詳細講解。框架
Lambda表達式簡介
Lambda表達式是一種匿名函數(對Java而言這並不徹底準確),通俗的說,它是沒有聲明的方法,即沒有訪問修飾符、返回值聲明和名字的方法。使用Lambda表達式的好處很明顯就是可使代碼變的更加簡潔緊湊。編程語言
Lambda表達式的使用場景與匿名類的使用場景幾乎一致,都是在某個功能(方法)只使用一次的時候。ide
Lambda表達式語法結構
Lambda表達式一般使用(param)->(body)語法書寫,基本格式以下:
//沒有參數 () -> body // 1個參數 (param) -> body // 或 (param) ->{ body; } // 多個參數 (param1, param2...) -> { body } // 或 (type1 param1, type2 param2...) -> { body }
常見的Lambda表達式以下:
// 無參數,返回值爲字符串「公衆號:程序新視界」 () -> "公衆號:程序新視界"; // 1個String參數,直接打印結果 (System.out::println); // 或 (String s) -> System.out.print(s) // 1個參數(數字),返回2倍值 x -> 2 * x; // 2個參數(數字),返回差值 (x, y) -> x – y // 2個int型整數,返回和值 (int x, int y) -> x + y
對照上面的示例,咱們再總結一下Lambda表達式的結構:
- Lambda表達式能夠有0~n個參數。
- 參數類型能夠顯式聲明,也可讓編譯器從上下文自動推斷類型。如(int x)和(x)是等價的。
- 多個參數用小括號括起來,逗號分隔。一個參數能夠不用括號。
- 沒有參數用空括號表示。
- Lambda表達式的正文能夠包含零條,一條或多條語句,若是有返回值則必須包含返回值語句。若是隻有一條可省略大括號。若是有一條以上則必須包含在大括號(代碼塊)中。
函數式接口
函數式接口(Functional Interface)是Java8對一類特殊類型的接口的稱呼。這類接口只定義了惟一的抽象方法的接口(除了隱含的Object對象的公共方法),所以最開始也就作SAM類型的接口(Single Abstract Method)。
好比上面示例中的java.lang.Runnable就是一種函數式接口,在其內部只定義了一個void run()的抽象方法,同時在該接口上註解了@FunctionalInterface。
@FunctionalInterface public interface Runnable { public abstract void run(); }
@FunctionalInterface註解是用來表示該接口要符合函數式接口的規範,除了隱含的Object對象的公共方法之外只可有一個抽象方法。固然,若是某個接口只定義一個抽象方法,不使用該註解也是可使用Lambda表達式的,可是沒有該註解的約束,後期可能會新增其餘的抽象方法,致使已經使用Lambda表達式的地方出錯。使用@FunctionalInterface從編譯層面解決了可能的錯誤。
好比當註解@FunctionalInterface以後,寫兩個抽象方法在接口內,會出現如下提示:
Multiple non-overriding abstract methods found in interface com.secbro2.lambda.NoParamInterface
經過函數式接口咱們也能夠得出一個簡單的結論:可以使用Lambda表達式的接口,只能有一個抽象方法(除了隱含的Object對象的公共方法)。
注意此處的方法限制爲抽象方法,若是接口內有其餘靜態方法則不會受限制。
方法引用,雙冒號操做
[方法引用]的格式是,類名::方法名。
像如ClassName::methodName或者objectName::methodName的表達式,咱們把它叫作方法引用(Method Reference),一般用在Lambda表達中。
看一下示例:
// 無參數狀況 NoParamInterface paramInterface2 = ()-> new HashMap<>(); // 可替換爲 NoParamInterface paramInterface1 = HashMap::new; // 一個參數狀況 OneParamInterface oneParamInterface1 = (String string) -> System.out.print(string); // 可替換爲 OneParamInterface oneParamInterface2 = (System.out::println); // 兩個參數狀況 Comparator c = (Computer c1, Computer c2) -> c1.getAge().compareTo(c2.getAge()); // 可替換爲 Comparator c = (c1, c2) -> c1.getAge().compareTo(c2.getAge()); // 進一步可替換爲 Comparator c = Comparator.comparing(Computer::getAge);
再好比咱們用函數式接口java.util.function.Function來實現一個String轉Integer的功能,能夠以下寫法:
Function<String, Integer> function = Integer::parseInt; Integer num = function.apply("1");
根據Function接口的定義Function<T,R>,其中T表示傳入類型,R表示返回類型。具體就是實現了Function的apply方法,在其方法內調用了Integer.parseInt方法。
經過上面的講解,基本的語法已經完成,如下內容經過實例來逐一演示在不一樣的場景下如何使用。
Runnable線程初始化示例
Runnable線程初始化是比較典型的應用場景。
// 匿名函類寫法 new Thread(new Runnable() { @Override public void run() { System.out.println("歡迎關注公衆號:程序新視界"); } }).start(); // lambda表達式寫法 new Thread(() -> System.out.println("歡迎關注公衆號:程序新視界")).start(); // lambda表達式 若是方法體內有多行代碼須要帶大括號 new Thread(() -> { System.out.println("歡迎關注公衆號"); System.out.println("程序新視界"); }).start();
一般都會把lambda表達式內部變量的名字起得短一些,這樣能使代碼更簡短。
事件處理示例
Swing API編程中常常會用到的事件監聽。
// 匿名函類寫法 JButton follow = new JButton("關注"); follow.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("已關注公衆號:程序新視界"); } }); // lambda表達式寫法 follow.addActionListener((e) -> System.out.println("已關注公衆號:程序新視界")); // lambda表達式寫法 follow.addActionListener((e) -> { System.out.println("已關注公衆號"); System.out.println("程序新視界"); });
列表遍歷輸出示例
傳統遍歷一個List,基本上都使用for循環來遍歷,Java8以後List擁有了forEach方法,可配合lambda表達式寫出更加簡潔的方法。
List<String> list = Arrays.asList("歡迎","關注","程序新視界"); // 傳統遍歷 for(String str : list){ System.out.println(str); } // lambda表達式寫法 list.forEach(str -> System.out.println(str)); // lambda表達式寫法 list.forEach(System.out::println);
函數式接口示例
在上面的例子中已經看到函數式接口java.util.function.Function的使用,在java.util.function包下中還有其餘的類,用來支持Java的函數式編程。好比經過Predicate函數式接口以及lambda表達式,能夠向API方法添加邏輯,用更少的代碼支持更多的動態行爲。
@Test public void testPredicate() { List<String> list = Arrays.asList("歡迎", "關注", "程序新視界"); filter(list, (str) -> ("程序新視界".equals(str))); filter(list, (str) -> (((String) str).length() == 5)); } public static void filter(List<String> list, Predicate condition) { for (String content : list) { if (condition.test(content)) { System.out.println("符合條件的內容:" + content); } } }
其中filter方法中的寫法還能夠進一步簡化:
list.stream().filter((content) -> condition.test(content)).forEach((content) ->System.out.println("符合條件的內容:" + content)); list.stream().filter(condition::test).forEach((content) ->System.out.println("符合條件的內容:" + content)); list.stream().filter(condition).forEach((content) ->System.out.println("符合條件的內容:" + content));
若是不須要「符合條件的內容:」字符串的拼接,還可以進一步簡化:
list.stream().filter(condition).forEach(System.out::println);
若是將調用filter方法的判斷條件也寫在一塊兒,test方法中的內容能夠經過一行代碼來實現:
list.stream().filter((str) -> ("程序新視界".equals(str))).forEach(System.out::println);
若是須要同時知足兩個條件或知足其中一個便可,Predicate能夠將這樣的多個條件合併成一個。
Predicate start = (str) -> (((String) str).startsWith("程序")); Predicate len = (str) -> (((String) str).length() == 5); list.stream().filter(start.and(len)).forEach(System.out::println);
Stream相關示例
在《JAVA8 STREAM新特性詳解及實戰》一文中已經講解了Stream的使用。你是否發現Stream的使用都離不開Lambda表達式。是的,全部Stream的操做必須以Lambda表達式爲參數。
以Stream的map方法爲例:
Stream.of("a","b","c").map(item -> item.toUpperCase()).forEach(System.out::println); Stream.of("a","b","c").map(String::toUpperCase).forEach(System.out::println);
更多的使用實例可參看Stream的《JAVA8 STREAM新特性詳解及實戰》一文。
Lambda表達式與匿名類的區別
- 關鍵詞的區別:對於匿名類,關鍵詞this指向匿名類,而對於Lambda表達式,關鍵詞this指向包圍Lambda表達式的類的外部類,也就是說跟表達式外面使用this表達的意思是同樣。
- 編譯方式:Java編譯器編譯Lambda表達式時,會將其轉換爲類的私有方法,再進行動態綁定,經過invokedynamic指令進行調用。而匿名內部類仍然是一個類,編譯時編譯器會自動爲該類取名並生成class文件。
其中第一條,以Spring Boot中ServletWebServerApplicationContext類的一段源碼做爲示例:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } }
其中,這裏的this指向的就是getSelfInitializer方法所在的類。
小結
至此,Java8 Lambda表達式的基本使用已經講解完畢,最關鍵的仍是要勤加練習,達到熟能生巧的使用。固然,剛開始可能須要一個適應期,在此期間能夠把本篇文章收藏當作一個手冊拿來參考。
原文連接:《Java8 Lambda表達式詳解手冊及實例》 <br>
<center><b>程序新視界</b>:精彩和成長都不容錯過</center>