摘要:初體驗下面進入本文的正題表達式。接下來展示表達式和其好基友的配合。吐槽一下方法引用表面上看起來方法引用和構造器引用進一步簡化了表達式的書寫,但是個人覺得這方面沒有的下劃線語法更加通用。
感謝同事【天錦】的投稿。投稿請聯系 tengfei@ifeve.com
本文主要記錄自己學習Java8的歷程,方便大家一起探討和自己的備忘。因為本人也是剛剛開始學習Java8,所以文中肯定有錯誤和理解偏差的地方,希望大家幫忙指出,我會持續修改和優化。本文是該系列的第一篇,主要介紹Java8對屌絲碼農最有吸引力的一個特性—lambda表達式。
java8的安裝工欲善其器必先利其器,首先安裝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表達式。以下是維基百科上對于”Lambda expression”的解釋:
a function (or a subroutine) defined, and possibly called, without being bound to an identifier。
簡單點說就是:一個不用被綁定到一個標識符上,并且可能被調用的函數。這個解釋還不夠通俗,lambda表達式可以這樣定義(不精確,自己的理解):一段帶有輸入參數的可執行語句塊。這樣就比較好理解了吧?一例勝千言。有讀者反饋:不理解Stream的含義,所以這里先提供一個沒用stream的lambda表達式的例子。
//這里省略list的構造 Listnames = ...; Collections.sort(names, (o1, o2) -> o1.compareTo(o2));
//這里省略list的構造 Listnames = ...; Collections.sort(names, new Comparator () { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } });
上面兩段代碼分別是:使用lambda表達式來排序和使用匿名內部類來排序。這個例子可以很明顯的看出lambda表達式簡化代碼的效果。接下來展示lambda表達式和其好基友Stream的配合。
Listnames = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); List lowercaseNames = names.stream().map((String name) -> {return name.toLowerCase();}).collect(Collectors.toList());
這段代碼就是對一個字符串的列表,把其中包含的每個字符串都轉換成全小寫的字符串(熟悉Groovy和Scala的同學肯定會感覺很親切)。注意代碼第四行的map方法調用,這里map方法就是接受了一個lambda表達式(其實是一個java.util.function.Function的實例,后面會介紹)。
為什么需要Lambda表達式呢?在嘗試回答這個問題之前,我們先看看在Java8之前,如果我們想做上面代碼的操作應該怎么辦。
先看看普通青年的代碼:
Listnames = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); List lowercaseNames = new ArrayList<>(); for (String name : names) { lowercaseNames.add(name.toLowerCase()); }
接下來看看文藝青年的代碼(借助Guava):
Listnames = new ArrayList<>(); names.add("TaoBao"); names.add("ZhiFuBao"); List lowercaseNames = FluentIterable.from(names).transform(new Function () { @Override public String apply(String name) { return name.toLowerCase(); } }).toList();
在此,我們不再討論普通青年和文藝青年的代碼風格孰優孰劣(有興趣的可以去google搜索“命令式編程vs聲明式編程”)。本人更加喜歡聲明式的編程風格,所以偏好文藝青年的寫法。但是在文藝青年代碼初看起來看起來干擾信息有點多,Function匿名類的構造語法稍稍有點冗長。所以Java8的lambda表達式給我們提供了創建SAM(Single Abstract Method)接口更加簡單的語法糖。
Lambda語法詳解我們在此抽象一下lambda表達式的一般語法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }
從lambda表達式的一般語法可以看出來,還是挺符合上面給出的非精確版本的定義–“一段帶有輸入參數的可執行語句塊”。
上面的lambda表達式語法可以認為是最全的版本,寫起來還是稍稍有些繁瑣。別著急,下面陸續介紹一下lambda表達式的各種簡化版:
參數類型省略–絕大多數情況,編譯器都可以從上下文環境中推斷出lambda表達式的參數類型。這樣lambda表達式就變成了:
(param1,param2, ..., paramN) -> { statment1; statment2; //............. return statmentM; }
所以我們最開始的例子就變成了(省略了List的創建):
ListlowercaseNames = names.stream().map((name) -> {return name.toLowerCase();}).collect(Collectors.toList());
當lambda表達式的參數個數只有一個,可以省略小括號。lambda表達式簡寫為:
param1 -> { statment1; statment2; //............. return statmentM; }
所以最開始的例子再次簡化為:
ListlowercaseNames = names.stream().map(name -> {return name.toLowerCase();}).collect(Collectors.toList());
當lambda表達式只包含一條語句時,可以省略大括號、return和語句結尾的分號。lambda表達式簡化為:
param1 -> statment
所以最開始的例子再次簡化為:
ListlowercaseNames = names.stream().map(name -> name.toLowerCase()).collect(Collectors.toList());
使用Method Reference(具體語法后面介紹)
//注意,這段代碼在Idea 13.0.2中顯示有錯誤,但是可以正常運行 ListLambda表達式眼中的外部世界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中,this不是指向lambda表達式產生的那個SAM對象,而是聲明它的外部對象。
方法引用(Method reference)和構造器引用(construct reference)方法引用
前面介紹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的下劃線語法更加通用。比較才能看出,翠花,上代碼!
Listnames = 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表達式語法
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65131.html
摘要:第一個函數生成一個新的實例第二個函數接受兩個參數,第一個是前面生成的對象,二個是中包含的元素,函數體就是把中的元素加入對象中。 感謝同事【天錦】的投稿。投稿請聯系 tengfei@ifeve.com 上篇文章[Java8初體驗(一)lambda表達式語法]()比較詳細的介紹了lambda表達式的方方面面,細心的讀者會發現那篇文章的例子中有很多Stream的例子。這些Stream的例子可...
摘要:實際上方法引用是表達式的一種語法糖。小結本篇全面介紹了方法引用的四種使用方式,且每種方式都有對應一個示例來幫助大家理解。 上一篇我們詳細介紹了Optional類用來避免空指針問題,本篇我們全面了解一下Java8中的方法引用特性。方法引用是lambda表達式的一種特殊形式,如果正好有某個方法滿足一個lambda表達式的形式,那就可以將這個lambda表達式用方法引用的方式表示,但是如果這...
摘要:一表達式匿名內部類最大的問題在于其冗余的語法,比如前面的中五行代碼僅有一行是在執行任務。總結基于詞法作用域的理念,表達式不可以掩蓋任何其所在上下文的局部變量。 轉載請注明出處:https://zhuanlan.zhihu.com/p/20540175 在介紹Lambda表達式之前,我們先來看只有單個方法的Interface(通常我們稱之為回調接口): public interface...
摘要:大家好,我是樂字節的小樂,上一次我們說到了核心特性之函數式接口,接下來我們繼續了解又一核心特性方法引用。方法引用是一種更簡潔易懂的表達式。感謝光臨閱讀小樂的,敬請關注樂字節后續將繼續講述等前沿知識技術。 大家好,我是樂字節的小樂,上一次我們說到了Java8核心特性之函數式接口,接下來我們繼續了解Java8又一核心特性——方法引用。 showImg(https://segmentfaul...
摘要:函數式編程說前,先理解下什么是函數式編程,如果你是個純程序員,而且之前一直是沒有使用過,可能還沒有使用過這種編程方式。表達式可以表示閉包注意和數學傳統意義上的不同。意思就是說,只要是接口類型,我們都可以傳入表達式。在包下定義了各種函數接口 函數式編程 說lambdas前,先理解下什么是函數式編程,如果你是個純Java程序員,而且之前一直是沒有使用過Java8,可能還沒有使用過這種編程方...
閱讀 1302·2021-11-23 09:51
閱讀 3405·2021-09-06 15:00
閱讀 990·2021-08-16 10:57
閱讀 1376·2019-08-30 12:46
閱讀 942·2019-08-29 12:22
閱讀 1610·2019-08-29 11:07
閱讀 3153·2019-08-26 11:23
閱讀 2987·2019-08-23 15:14