詳解Lambda
Java8已經更新了好久了。變化很大,但感覺有用的不多。其中最廣為人知的就是Lambda
表達式??雌饋肀容^蛋疼,感覺Java
越來越C
化了。
當初以為Lambda
的作用就是為了簡化匿名內部類的輸寫,最近看了些文章才發現是我自己膚淺了。Java
更新一個大版本是又怎么會只是這樣小小的功能
Lambda表示式
Lambda
的基本格式為()->內容
,以箭頭為分隔,左邊為參數區,右邊為代碼區.
可以把它看成是一個臨時定義的方法,沒有方法名,默認為public
,根據代碼區里的有沒有return
來決定返回值.如果沒有return
則是void
.
- 單行代碼可以省略return
- 多行代碼使用
{}
來包裹. - 代碼內可以引用外部局部變量,實際上是隱式的把變量變成
final
網上用得最多的例子就是(x,y)->x+y;
,很蛋疼的例子,當初看Junit
單元測試時也是這樣的例子.
來個實際點的例子吧.
public boolean isEmpty(String text){
return text==null||"".eqauls(text)
}
用Lambda是(text)->return text==null||"".eqauls(text);
看到這里估計很多人會覺得更蛋疼了.明明Lambda
使用是的匿名內部類的簡寫,為什么要拿方法來做比較,另外明明有方法可以調用,何必要用Lambda
Lambda的起源
曾經無數次的羨慕js
有閉包,可以把方法當做參數來傳遞.在項目中有很多類都存在著很類似的方法
public String getIds(List<T> list){
if(list==null||list.isEmpty){
return null;
}
StringBuilder builder = new StringBuilder();
for(T t:list){
String id=doSomething(t)
builder.append(",").append(id);
}
return builder.deleteCharAt(0).toString();
}
想抽取一下,奈何每個類里T
不同,doSomething
的實現也不同.T
可以用泛型,doSomething(T t)
這個方法怎么傳呢??
面向接口吧.我們可以定義一個接口來干這個事.
public interface GetId<T>{
String getId(T t);
}
上面的方法就可以抽到工具類中了
public static <T> String getIds(List<T> list,GetId<T> interf){
if(list==null||list.isEmpty){
return null;
}
StringBuilder builder = new StringBuilder();
for(T t:list){
String id=interf.getId(t);
builder.append(",").append(id);
}
return builder.deleteCharAt(0).toString();
}
說來說去好像不關Lambda
什么的事呀.實際上上面的例子就是Lambda的起源.
隨著函數式編程廣為程序員喜愛,Java也在考慮加入函數式編程.但有兩個問題,一 做為一個強類型語言,類型轉換有點蛋疼,二 不能把方法當成參數(有返回值的還好,void
方法當參數是相當的無語的).
Java
考慮了半天,使用了上面的例子的邏輯來處理,用接口來包裝方法,把接口當參數來代替.
上面的例子中.在類中調用如下
String ids=Utils.getIds(List<XXX> list,new GetId<XXX>(){
getId(XXX x){
return ooxx(x);
}
});
這是一個參數,如果是多個參數那不要嚇死人.
String ids=Utils.getIds(List<XXX> list,
new GetId<XXX>(){
getId(XXX x){
return ooxx(x);
}
},
new GetId<XXX>(){
getId(XXX x){
return xxoo(x);
}
},
new GetId<XXX>(){
getId(XXX x){
return oxox(x);
}
}
);
所以Lambda
第一個作用就體現出來了簡化書寫,好寫好看。如
String ids=Utils.getIds(List<XXX> list,(x)->ooxx(x));
這里有點奇怪x
是什么的鬼???,其實完整的應該是
String ids=Utils.getIds(List<XXX> list,(XXX x)->ooxx(x));
這里就體現了lambda
的牛逼之處類型推導(準確來說是Java8的特性),編譯時推導參數類型和返回類型.
最上面那個坑爹的例子估計很多人都會像我當初一樣蒙B。(x,y)->x+y;
,這x和y是什么的東東??
實際上這個例子應該是(int x,int y)->return x+y;
,這樣看就清楚多了.傳兩個int,返回其和.
再給個實際點的例子吧
String[] strings={"xx","oo","xxoo"};
傳統的排序是這樣的
Arrays.sort(strings,new Comparetor<String>(){
public int compareTo(String s1,String s2){
return s1.length-s2.length;
}
})
使用lambda是這樣的
Arrays.sort(strings,(String s1,String s2)->return s1.length-s2.length);
或者
Arrays.sort(strings,(s1,s2)->s1.length-s2.length);
或者
Arrays.sort(strings,(s1,s2)->{
int x= s1.length;
int y=s2.length;
return x-y;
});
Lambda的實現
lambda
的原理就是以把接口當參數傳遞的方式來形成閉包.所以這個接口只能定義一個方法,這種接口叫函數接口
.這種接口可以隱式轉為lambda
為了防止該接口的單方法性被破壞,Java8
定義了一個注解FunctionInterface
,Java庫中所有這種接口都已經添加了這個注解。如
@FunctionInterface
public Interface Runable{
void run();
}
當然,自己也可以定義函數接口,很簡單,只要是單方法都可以。最好是加上注解,防止自己或別人去添加新的方法.
另外Java8中提供了很多常用的接口,免得自己去創建。這些接口放在java.utils.function
包里.
默認的接口可接收和返回的基本數據只有int
,long
,double
三種,String
視為對象。
- Custmer一家,提供了
void
類的接口,可以接收單個或兩個的基本數據類型或對象的參數(無參的有現成的Runable
)。如
interface Custmer<T>{
void apply(T t)
}
interface BiConsumer<T,U>{
void apply(T t,U u);
}
另外還支持對象+基本數據類型的參數
- Predicate一家,提供了可接收1-2個基本數據類型或對象的參數,返回
boolean
的接口(無參的是booleanSupplier
接口) - Function,Oprator,Supplier一家,提供了接收0-2個參數,返回基本數據類型或對象的接口
- Function一家可接收1-2個參數,返回的是對象
- Supplier一家不接收參數,返回基本數據類型和對象
- Operator一家接收1-2個參數,返回同類型的數據。
unary
前綴的是接收一個參數,binary
前綴的是接收兩個參數。那個坑爹的(x,y)->x+y
的例子就是由IntBinaryOperator
接口來實現的.
interface IntBinaryOperator{
int applyAsInt(int left,int right);
}
默認提供的接口,如果是兩個基本數據類型的參數,則參數的類型都必須是相同的.也就是說兩個以內的參數基本上不用自己定義函數接口了.上面的例子就可以這樣寫
public static <T> String getIds(List<T> list,Function<T,String> interf){
}
使用時如下
Utils.getIds(list,t->ooxx(t);)
函數式編程
函數式編程需要可以把函數當成參數,在Java中所有的東西都是需要有類型的,只有函數自身是沒有的。Lambda
以間接的方式提供了函數的類型,及類型的推導,為函數式編程清掃了最大的障礙。再配合新提供的Stream
,在java中終與可以愉快的的使用函數式編程了。
然而....沒有Lambda,沒有Java8,我用Rxjava一樣可以愉快的函數式編程.