Lambda是java8出的新特性,之前很少用Lambda表達(dá)式寫代碼,現(xiàn)在慢慢習(xí)慣用Lambda表達(dá)式,并且記得Lambda表達(dá)式的知識,方便日后查閱和溫習(xí)。
Lambda表達(dá)式
Lambda表達(dá)式是在java8版本中引入的一種新的語法元素和操作符,這個操作符為“->”。
為了更加掌握Lambda表達(dá)式的使用,從那下面幾個方面去學(xué)習(xí)lambda表達(dá)式:
- 什么是Lambda
- Lambda的用途意義
- Lambda的語法
- Lambda表達(dá)式的語法格式
- Lambda的函數(shù)式接口@FunctionalInterface的使用說明
- 了解Java內(nèi)置函數(shù)式接口
- 方法引用
- 構(gòu)造器引用
- 數(shù)組引用
- 集合排序
- list遍歷
- 返回集合中符合條件的元素
- 對象間的元素比較
什么是Lambda
Lambda就是一個匿名函數(shù),其實(shí)可理解為沒有函數(shù)名的函數(shù)。其實(shí)就是函數(shù)式接口的一種表現(xiàn)形式。
Lambda的用途意義
就是讓你的代碼更加簡潔,使用起來更加方便。同時也是簡化了匿名委托的使用,這里出現(xiàn)了一個專有名詞匿名委托,那么什么是匿名委托呢?下面通過代碼了解匿名委托。
public class LambdaTest{
public static void main(Strign[] args){
//一個匿名內(nèi)部類 java7之前
Runnable runnable = new Runnable(){
@Override
public void run(){
System.out.println("普通寫法");
}
} ;
//java8 用lambda表達(dá)式
Runnable rb = ()->{
System.out.println("lambda表達(dá)式寫法");
};
}
}
上面代碼看出,new Runnable(){}就是一個匿名委托。如果都將一塊代碼塊賦值給變量,那么java7之前寫法與java8的lambda表達(dá)式的寫法,很顯然lambda表達(dá)式寫法更加簡潔優(yōu)雅,代碼量也少,也達(dá)到了簡化匿名委托的效果。
Lambda的語法
Lambda表達(dá)式形式:(參數(shù))->{}
- 左側(cè):指定了Lambda表達(dá)式所需要的所有參數(shù)。
- 右側(cè):指定了Lambda體,Lambda體中是Lambda表達(dá)式執(zhí)行的功能代碼塊。
下面通過一個無參與帶參的Lambda表達(dá)式作例子說明,并與普通寫法作對比,這樣更加容易理解和掌握lambda表達(dá)式。
//無參寫法
Runnable runnable = new Runnable(){
@Override
public void run(){
System.out.println("普通寫法");
}
} ;
//java8 用lambda表達(dá)式
Runnable rb = ()->System.out.println("lambda表達(dá)式寫法");
//帶參寫法
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//執(zhí)行的功能代碼塊
}
});
mListView.setOnItemClickListener((parent, view, position, id) -> {
//執(zhí)行的功能代碼塊
});
上述代碼簡單易懂,那么我們進(jìn)一步去學(xué)習(xí)Lambda表達(dá)式的語法和用法。
Lambda表達(dá)式的語法格式
下面我們就了解一下Lambda表達(dá)式的語法格式有那些:
-
語法格式一:無參數(shù),無返回值。
()->System.out.println("lambda表達(dá)式寫法");
-
語法格式二:有一個參數(shù),并且無返回值。
(a)->System.out.println(a);
-
語法格式三:只有一個參數(shù),小括號可以省略不寫。
a->System.out.println(a);
-
語法格式四:如果Lambda體中只有一條代碼語句,return和大括號都可以省略不寫。
(int a,int b)->a+b;
-
語法格式五:有兩個或兩個以上的參數(shù),有返回值,并且Lambda體中有多條代碼語句。
(int a,int b)->{ System.out.println("函數(shù)式接口"); return a+b; };
-
語法格式六:Lambda表達(dá)式的參數(shù)列表的數(shù)據(jù)類型可以省略不寫,因?yàn)镴VM編譯器通過上下文推斷出數(shù)據(jù)類型,即“類型推斷”。
(Integer a,Integer b)->Integer.compare(a,b);
下面通過代碼實(shí)現(xiàn)上面的六種語法格式去學(xué)習(xí),當(dāng)然還有一些高級用法(如Stream中使用Lambda表達(dá)式,Stream放到一下次去學(xué)習(xí)),我們先從簡單的基礎(chǔ)的開始學(xué)習(xí):
public class TestLambda {
public static void main(String []args){
//語法格式一
LambdaCallback1 callback1 = ()->System.out.println("無參數(shù),無返回值");
callback1.test();
//語法格式二
LambdaCallback2 callback21 = (s)->System.out.println(s);
callback21.test("語法格式二");
//語法格式三
LambdaCallback2 callback22 = s->System.out.println(s);
callback22.test("語法格式三");
//語法格式四
LambdaCallback4 callback31 = (a,b)->a+b;
System.out.println("語法格式四 "+callback31.test(4,5));
//語法格式五
LambdaCallback4 callback32 = (a,b)->{
System.out.println("語法格式五 函數(shù)式接口");
return a+b;
};
System.out.println("語法格式五 "+callback32.test(1,9));
//語法格式六
LambdaCallback3 callback4= (s,a)->{
System.out.println("s: "+s);
System.out.println("s: "+a);
};
callback4.test("語法格式六",110);
}
@FunctionalInterface
public interface LambdaCallback1{
void test();
}
@FunctionalInterface
public interface LambdaCallback2{
void test(String str);
}
@FunctionalInterface
public interface LambdaCallback3{
void test(String str,int a);
}
@FunctionalInterface
public interface LambdaCallback4{
int test(int str,int a);
}
}
Lambda的函數(shù)式接口@FunctionalInterface的使用說明
- Lambda表達(dá)式需要“函數(shù)式接口“的支持,函數(shù)式接口:接口中有且僅有一個抽象方法的接口,稱作為函數(shù)式接口。
- 使用注解@FunctionalInterface修飾,表示檢查此接口是一個符合Java語言規(guī)范定義的函數(shù)式接口。
- 函數(shù)式接口默認(rèn)繼承java.lang.Object,如果重寫了java.lang.Object中的抽象方法,也不算是抽象方法
如果一個接口中包含不止一個抽象方法,那么使用@FunctionalInterface注解編譯時會報(bào)錯。例子如下代碼:
@FunctionalInterface
public interface LambdaCallback5{
void test();
int sum(int a,int b);
}
上述LambdaCallback5類編譯時報(bào)錯:multiple non-overriding abstract methods found in interface Action
@FunctionalInterface的正確使用如上述代碼的LambdaCallback1~4接口。下面這個接口也是一個正確的函數(shù)式接口:
@FunctionalInterface
public interface LambdaCallback6 {
// 抽象方法
void add();
// java.lang.Object中的方法不是抽象方法 屬于重寫Object中的方法
boolean equals(Object var1);
// default 不是抽象方法 默認(rèn)方法,通過實(shí)現(xiàn)類直接調(diào)用,不需要實(shí)現(xiàn)類實(shí)現(xiàn)該方法,提高了擴(kuò)展性。
default void defaultMethod(){
}
// static 不是抽象方法 靜態(tài)方法,直接接口調(diào)用。跟普通的static方法是一樣的,不需要實(shí)現(xiàn)類去調(diào)用。
static void staticMethod(){
}
}
默認(rèn)(default)方法只能通過接口的實(shí)現(xiàn)類來調(diào)用,不需要實(shí)現(xiàn)方法,也就是說接口的實(shí)現(xiàn)類可以繼承或者重寫默認(rèn)方法。
靜態(tài)(static)方法只能通過接口名調(diào)用,不可以通過實(shí)現(xiàn)類的類名或者實(shí)現(xiàn)類的對象調(diào)用。就跟普通的靜態(tài)類方法一樣,通過方法所在的類直接調(diào)用。
有人會想如果不添加@FunctionalInterface注解的接口能不能支持lambda表達(dá)式,其實(shí)通過上面使用說明的第二點(diǎn)特點(diǎn),就可以知道,不添加@FunctionalInterface注解,只要接口符合函數(shù)式接口
也是支持lambda表達(dá)式:看下面代碼:
public class TestLambda {
@Test
public void mainTest(){
LambdaCallback7 callback7= (name,age)->"名字: "+name+" 年齡: "+age;
System.out.println(callback7.test("lambda",3));
onTest(callback7,"android",5);
}
private void onTest(LambdaCallback7 callback7,String name,int age){
System.out.println(callback7.test(name,age));
}
public interface LambdaCallback7{
String test(String name,int age);
}
}
//打印出的結(jié)果為
I/System.out: 名字: lambda 年齡: 3
I/System.out: 名字: android 年齡: 5
找到源碼驗(yàn)證,其實(shí)就是JDK中有默認(rèn)支持處理。如果是單接口(單接口是指一個接口類里有且僅有一個抽象方法)
就可以轉(zhuǎn)lambda表達(dá)式。@FunctionalInterface注解不是必須的,加不加都無所有了,如果添加了這個注解只是方便了編譯器檢查和提示作用而已,如果不符合函數(shù)式接口的定義,則注解會報(bào)錯,這樣減少一些誤操作。同時也驗(yàn)證了@FunctionalInterface的使用說明中的第二點(diǎn)特點(diǎn)。
了解Java內(nèi)置函數(shù)式接口
平時我們在寫安卓代碼時,你也會發(fā)現(xiàn)那些xxxListener接口也是內(nèi)置的函數(shù)式接口,如OnClickListener、OnLongClickListener、OnTouchListener等等。前面寫到過的Runnable接口也是內(nèi)置的函數(shù)式接口。下面例舉一些java內(nèi)置的函數(shù)式接口。
接口 | 參數(shù) | 返回類型 | 示例 |
---|---|---|---|
Predicate<T> | T | boolean | 我是斷言函數(shù)式接口 |
Consumer<T> | T | void | 輸出一個值 |
Function<T,R> | T | R | 獲取Student對象的分?jǐn)?shù) |
Supplier<T> | None | T | 工廠方法 |
UnaryOperator<T> | T | T | 邏輯非(!) |
BinaryOperator<T> | (T,T) | T | 求兩個數(shù)的最大值 |
例子:
//xxxListener
//普通寫法
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//TODO
}
});
//lambda表達(dá)式寫法
findViewById(R.id.button).setOnClickListener( v->{
//TODO
});
//Consumer
Consumer<String> consumer1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer1.accept("普通寫法");
Consumer<String> consumer2 = s->System.out.println(s);
consumer2.accept("lambda內(nèi)置寫法");
//對象的方法引用 下面會詳細(xì)講
Consumer<String> consumer3 = System.out::println;
consumer3.accept("對象的方法引用的寫法");
方法引用
當(dāng)要傳遞給Lambda體的操作已經(jīng)有了實(shí)現(xiàn)的方法時,可以使用方法引用。(實(shí)現(xiàn)抽象方法的參數(shù)列表必須與方法引用方法的參數(shù)列表保持一致)
方法引用:使用操作符“ :: ”將方法名和對象或類的名字分隔開( ClassName::MethodName )。有三種主要使用情況,如下:
- 對象::實(shí)例方法
- 類::靜態(tài)方法
- 類::實(shí)例方法
/**
* Consumer的作用顧名思義,是給定義一個參數(shù),對其進(jìn)行(消費(fèi))處理,處理的方式可以是任意操作。
* accept(T t)是一個抽象方法。對給定的參數(shù)T執(zhí)行定義的操作。
*/
Consumer<String> consumer2 = s->System.out.println(s);
consumer2.accept("lambda內(nèi)置寫法");
Consumer<String> consumer3 = System.out::println;
consumer3.accept("對象的方法引用的寫法");
/**
* BinaryOperator表示對兩個相同類型的操作數(shù)的操作。產(chǎn)生與操作數(shù)相同類型的結(jié)果。
* 這是BiFunction的一個特殊例子,它的結(jié)果和操作數(shù)都是相同的數(shù)據(jù)類型。
* 這個是一個函數(shù)式接口,該接口有一個函數(shù)方法apply(Object,Object)。
*/
//取兩個數(shù)的最大值
BinaryOperator<Double> bo1 = (d1,d2)->Math.max(d1,d2);
System.out.println("最大值為:"+bo1.apply(10.2d,20d));
BinaryOperator<Double> bo2 = Math::max;
System.out.println("最大值為:"+bo2.apply(11.9d,10.11d));
上述代碼:consumer2與consumer3和bo1與bo2的效果都是一樣的,只是他們所用的語法糖不一樣而已。
構(gòu)造器引用
格式:ClassName :: new
與函數(shù)式接口相結(jié)合,自動與函數(shù)式接口中方法兼容。可以把構(gòu)造器引用賦值給定義方法,與構(gòu)造器參數(shù)列表要與接口中抽象方法的參數(shù)列表一致。
public class Student{
private String name;
private String subject;
private Integer score;
public Student(){}
public Student(String name) {
this.name = name;
}
public Student(String name, String subject, Integer score) {
this.name = name;
this.subject = subject;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
//函數(shù)式接口
public interface ScoreCompareInterface<T> {
//比較分?jǐn)?shù)
Integer compareScore(T t1,T t2);
}
public static void main(String []args){
Supplier<Student> sl1 = ()->new Student();
Supplier<Student> sl2 = Student::new;
Function<String,Student> f1 = name->new Student(name);
Function<String,Student> f2 = Student::new;
}
}
數(shù)組引用
格式:type[] :: new
Function<Integer,Student[]> fun1 = size->new Student[size];
Function<Integer,Student[]> fun2 = Student[]::new;
集合排序
對學(xué)生對象集合按數(shù)學(xué)科目分?jǐn)?shù)進(jìn)行排序。
public static void main(String []args){
Student s1 = new Student("張三","數(shù)學(xué)",90);
Student s2 = new Student("李四","數(shù)學(xué)",96);
Student s3 = new Student("王老五","數(shù)學(xué)",10);
List<Student> studentList = new ArrayList<>();
studentList.add(s1);
studentList.add(s2);
studentList.add(s3);
//排序
Collections.sort(studentList,(st1, st2)->st1.getScore().compareTo(st2.getScore()));
System.out.println(studentList);
}
List遍歷
//list遍歷
studentList.forEach(item-> System.out.print(item.getScore() +" "));
返回集合中符合條件的元素
返回集合中符合條件的元素(返回分?jǐn)?shù)大于或等于90分的學(xué)生對象)
//返回集合中符合條件的元素(返回分?jǐn)?shù)大于或等于90分的學(xué)生對象)
studentList.removeIf(item->item.getScore()<90);
studentList.forEach(item-> System.out.print(item.getScore() +" "));
removeIf的源碼,將集合中的每個元素放入test方法中,返回true則移除。
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
對象間的元素比較
比較兩個學(xué)生對象的分?jǐn)?shù) 如果不用lambda表達(dá)式,那么就要寫匿名內(nèi)部類,下面用lambda表達(dá)式:
ScoreCompareInterface<Student> scoreCompare =
(stu1,stu2)-> stu1.getScore().compareTo(stu2.getScore());
int compare = scoreCompare.compareScore(s1,s2);
if(compare==-1){
System.out.println(String.format("s1學(xué)生%s分?jǐn)?shù)%s 低于 s2學(xué)生%s分?jǐn)?shù)%s",
s1.getSubject(),s1.getScore(),s2.getSubject(),s2.getScore()));
}else if(compare==1){
System.out.println(String.format("s1學(xué)生%s分?jǐn)?shù)%s 高于 s2學(xué)生%s分?jǐn)?shù)%s",
s1.getSubject(),s1.getScore(),s2.getSubject(),s2.getScore()));
}else{
System.out.println(String.format("s1學(xué)生%s分?jǐn)?shù)%s 等于 s2學(xué)生%s分?jǐn)?shù)%s",
s1.getSubject(),s1.getScore(),s2.getSubject(),s2.getScore()));
}