感謝同事【天錦】的投稿。投稿請聯繫 tengfei@ifeve.comjava
本文主要記錄本身學習Java8的歷程,方便你們一塊兒探討和本身的備忘。由於本人也是剛剛開始學習Java8,因此文中確定有錯誤和理解誤差的地方,但願你們幫忙指出,我會持續修改和優化。本文是該系列的第一篇,主要介紹Java8對屌絲碼農最有吸引力的一個特性—lambda表達式。express
工欲善其器必先利其器,首先安裝JDK8。過程省略,你們應該均可以本身搞定。可是有一點這裏強調一下(Windows系統):目前咱們工做的版本通常是java 6或者java 7,因此不少人安裝java8基本都是學習爲主。這樣就在本身的機器上會存在多版本的JDK。並且你們通常是但願在命令行中執行java命令是基於老版本的jdk。可是在安裝完jdk8而且沒有設置path的狀況下,你若是在命令行中輸入:java -version,屏幕上會顯示是jdk 8。這是由於jdk8安裝的時候,會默認在C:/Windows/System32中增長java.exe,這個調用的優先級比path設置要高。因此即便path裏指定是老版本的jdk,可是執行java命令顯示的依然是新版本的jdk。這裏咱們要作的就是刪除C:/Windows/System32中的java.exe文件(不要手抖!)。編程
下面進入本文的正題–lambda表達式。首先咱們看一下什麼是lambda表達式。如下是維基百科上對於」Lambda expression」的解釋:併發
a function (or a subroutine) defined, and possibly called, without being bound to an identifier。app
簡單點說就是:一個不用被綁定到一個標識符上,而且可能被調用的函數。這個解釋還不夠通俗,lambda表達式能夠這樣定義(不精確,本身的理解):一段帶有輸入參數的可執行語句塊。這樣就比較好理解了吧?一例勝千言。有讀者反饋:不理解Stream的含義,因此這裏先提供一個沒用stream的lambda表達式的例子。ide
//這裏省略list的構造 List<String> names = ...; Collections.sort(names, (o1, o2) -> o1.compareTo(o2));
//這裏省略list的構造 List<String> names = ...; Collections.sort(names, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } });
上面兩段代碼分別是:使用lambda表達式來排序和使用匿名內部類來排序。這個例子能夠很明顯的看出lambda表達式簡化代碼的效果。接下來展現lambda表達式和其好基友Stream的配合。函數
List<String> names = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); List<String> lowercaseNames = names.stream().map((String name) -> {return name.toLowerCase();}).collect(Collectors.toList());
這段代碼就是對一個字符串的列表,把其中包含的每一個字符串都轉換成全小寫的字符串(熟悉Groovy和Scala的同窗確定會感受很親切)。注意代碼第四行的map方法調用,這裏map方法就是接受了一個lambda表達式(實際上是一個java.util.function.Function的實例,後面會介紹)。學習
爲何須要Lambda表達式呢?在嘗試回答這個問題以前,咱們先看看在Java8以前,若是咱們想作上面代碼的操做應該怎麼辦。優化
先看看普通青年的代碼:this
List<String> names = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); List<String> lowercaseNames = new ArrayList<>(); for (String name : names) { lowercaseNames.add(name.toLowerCase()); }
接下來看看文藝青年的代碼(藉助Guava):
List<String> names = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); List<String> lowercaseNames = FluentIterable.from(names).transform(new Function<String, String>() { @Override public String apply(String name) { return name.toLowerCase(); } }).toList();
在此,咱們再也不討論普通青年和文藝青年的代碼風格孰優孰劣(有興趣的能夠去google搜索「命令式編程vs聲明式編程」)。本人更加喜歡聲明式的編程風格,因此偏好文藝青年的寫法。可是在文藝青年代碼初看起來看起來干擾信息有點多,Function匿名類的構造語法稍稍有點冗長。因此Java8的lambda表達式給咱們提供了建立SAM(Single Abstract Method)接口更加簡單的語法糖。
咱們在此抽象一下lambda表達式的通常語法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }
從lambda表達式的通常語法能夠看出來,仍是挺符合上面給出的非精確版本的定義–「一段帶有輸入參數的可執行語句塊」。
上面的lambda表達式語法能夠認爲是最全的版本,寫起來仍是稍稍有些繁瑣。彆着急,下面陸續介紹一下lambda表達式的各類簡化版:
參數類型省略–絕大多數狀況,編譯器均可以從上下文環境中推斷出lambda表達式的參數類型。這樣lambda表達式就變成了:
(param1,param2, ..., paramN) -> { statment1; statment2; //............. return statmentM; }
因此咱們最開始的例子就變成了(省略了List的建立):
List<String> lowercaseNames = names.stream().map((name) -> {return name.toLowerCase();}).collect(Collectors.toList());
當lambda表達式的參數個數只有一個,能夠省略小括號。lambda表達式簡寫爲:
param1 -> { statment1; statment2; //............. return statmentM; }
因此最開始的例子再次簡化爲:
List<String> lowercaseNames = names.stream().map(name -> {return name.toLowerCase();}).collect(Collectors.toList());
當lambda表達式只包含一條語句時,能夠省略大括號、return和語句結尾的分號。lambda表達式簡化爲:
param1 -> statment
因此最開始的例子再次簡化爲:
List<String> lowercaseNames = names.stream().map(name -> name.toLowerCase()).collect(Collectors.toList());
使用Method Reference(具體語法後面介紹)
//注意,這段代碼在Idea 13.0.2中顯示有錯誤,可是能夠正常運行 List<String> lowercaseNames = names.stream().map(String::toLowerCase).collect(Collectors.toList());
咱們前面全部的介紹,感受上lambda表達式像一個閉關鎖國的傢伙,能夠訪問給它傳遞的參數,也能本身內部定義變量。可是卻歷來沒看到其訪問它外部的變量。是否是lambda表達式不能訪問其外部變量?咱們能夠這樣想:lambda表達式實際上是快速建立SAM接口的語法糖,原先的SAM接口均可以訪問接口外部變量,lambda表達式確定也是能夠(不但能夠,在java8中還作了一個小小的升級,後面會介紹)。
String[] array = {"a", "b", "c"}; for(Integer i : Lists.newArrayList(1,2,3)){ Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println); }
上面的這個例子中,map中的lambda表達式訪問外部變量Integer i。而且能夠訪問外部變量是lambda表達式的一個重要特性,這樣咱們能夠看出來lambda表達式的三個重要組成部分:
輸入參數
可執行語句
存放外部變量的空間
不過lambda表達式訪問外部變量有一個很是重要的限制:變量不可變(只是引用不可變,而不是真正的不可變)。
String[] array = {"a", "b", "c"}; for(int i = 1; i<4; i++){ Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println); }
上面的代碼,會報編譯錯誤。由於變量i被lambda表達式引用,因此編譯器會隱式的把其當成final來處理(ps:你們能夠想象問什麼上一個例子不報錯,而這個報錯。)細心的讀者確定會發現不對啊,之前java的匿名內部類在訪問外部變量的時候,外部變量必須用final修飾。Bingo,在java8對這個限制作了優化(前面說的小小優化),能夠不用顯示使用final修飾,可是編譯器隱式當成final來處理。
在lambda中,this不是指向lambda表達式產生的那個SAM對象,而是聲明它的外部對象。
方法引用
前面介紹lambda表達式簡化的時候,已經看過方法引用的身影了。方法引用能夠在某些條件成立的狀況下,更加簡化lambda表達式的聲明。方法引用語法格式有如下三種:
objectName::instanceMethod
ClassName::staticMethod
ClassName::instanceMethod
前兩種方式相似,等同於把lambda表達式的參數直接當成instanceMethod|staticMethod的參數來調用。好比System.out::println
等同於x->System.out.println(x)
;Math::max
等同於(x, y)->Math.max(x,y)
。
最後一種方式,等同於把lambda表達式的第一個參數當成instanceMethod的目標對象,其餘剩餘參數當成該方法的參數。好比String::toLowerCase
等同於x->x.toLowerCase()
。
構造器引用語法以下:ClassName::new
,把lambda表達式的參數當成ClassName構造器的參數 。例如BigDecimal::new
等同於x->new BigDecimal(x)
。
表面上看起來方法引用和構造器引用進一步簡化了lambda表達式的書寫,可是我的以爲這方面沒有Scala的下劃線語法更加通用。比較才能看出,翠花,上代碼!
List<String> names = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); names.stream().map(name -> name.charAt(0)).collect(Collectors.toList());
上面的這段代碼就是給定一個String類型的List,獲取每一個String的首字母,並將其組合成新的List。這段代碼就沒辦法使用方法引用來簡化。接下來,咱們簡單對比一下Scala的下劃線語法(沒必要太糾結Scala的語法,這裏只是作個對比):
//省略List的初始化 List[String] names = .... names.map(_.charAt(0))
在Scala中基本不用寫lambda表達式的參數聲明。
引用文檔
《Java SE 8 for the Really Impatient》
Java 8 Tutorial
Java 8 API doc
原創文章,轉載請註明: 轉載自併發編程網 – ifeve.com本文連接地址: Java8初體驗(一)lambda表達式語法