JAVA lambda 表達式
概要
Java8已經出來好幾年了,現在依然有很多人在用java7,在技術方面固步自封自然不是好事,接受新的事物,與時俱進纔不會被淘汰。面向對象編程,這麼多年一直很火熱,但是受制於語法的複雜,在近年來出現了一些優秀的函數是編程,在函數是編程的概念裏,方法也是甚至可以當作參數傳遞。Java8的lambda表達式其實就是想函數編程靠近的一種新的編程方式,它的語法簡單,減少了代碼的冗餘,相較與以往的內部類寫起來更簡單,下面我們來一起看看lambda的使用方式。
學習Lambda表達式必須得瞭解的概念:函數式接口。
在函數式編程語言中,Lambda表達式的類型是函數。
而在Java中,Lambda表達式是對象,它們必須依附於一類特別的對象類型——函數式接口(Functional Interface)。
接下來我們看下函數式接口的定義:
- 如果一個接口中,有且只有一個抽象的方法(Object類中的方法不包括在內),
- 那這個接口就可以被看做是函數式接口,它被@FunctionalInterface修飾。
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
上面是Runnable接口的聲明,在Java8後,Runnable接口多了一個FunctionalInterface註解,表示該接口是一個函數式接口。但是如果我們不添加FunctionalInterface註解的話,如果接口中有且只有一個抽象方法時,編譯器也會把該接口當做函數式接口看待。
2.1自定義一個只有一個方法的接口如下
public interface MyLambda<T> {
public T getData(T t);
}
2.2接口的實現調用
- 以前的實現方式,是直接用匿名內部類的方式
public class Main {
public static void main(String[] args) {
MyLambda<String> myLambda=new MyLambda<String>() {
@Override
public String getData(String s) {
return s+" test";
}
};
System.out.println(myLambda.getData( "hello" ));
}
}
- 替換成 Lambda
public class Main {
public static void main(String[] args) {
MyLambda<String> myLambda= s -> s+" test";
System.out.println(myLambda.getData( "hello" ));
}
}
由此可見,你可以簡單將lambda表達式看成匿名內部類的簡寫形式。我們來分析一下,仔細看上面兩部分的代碼,對比一下,第一部分的getData(String S),和第二部分的 s ->。其實s ->
就是對getData(String S)的實現,既然如此那麼我們當然可以把Lambda部分直接寫成 (String s)-> s+" test";或者是(s) -> s+" test";這個時候相比你以及該看出來了,”->”左側是方法的參數,右側是方法的實現。之所以左側可以省略類型,是因爲接口中只有一個方法,java內部推斷機制,可以直接推斷出類型,因此在實現的時候根本不需要指定類型。一般lambda有以下幾種類型。
- ()-> 66接口中的方法不需要參數,並且該方法的返回值爲 66
- (x)->x+”test” 該方法接受一個參數x,最後返回了一個x+”test”的返回值
- ()-> {
總結:->右側是對方法的實現,因此這部分是方法體的代碼,當方法體只有一句話的時候大括號可以省略,當多行的時候你需要加上大括號。
}
熟悉scala的同學,肯定對forEach不陌生。它可以迭代集合中所有的對象,java8中也提供了forEach()迭代功能。下面是java的集合類遍歷集合的操作,在這裏我們來探討一下ForEach()。
List<String> list = new ArrayList<>( );
list.add( "北京" );
list.add( "歡迎" );
list.add( "你" );
list.forEach( s -> System.out.println(s) );
看到這裏相比你就明白了,forEach裏面其實就是Lambda表達式,我們打開源碼看。
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception. Unless otherwise specified by the implementing class,
* actions are performed in the order of iteration (if an iteration order
* is specified). Exceptions thrown by the action are relayed to the
* caller.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
看見沒,forEach方法裏面的參數是一個Consumer<T> action接口,接着在for循環裏遍歷了Iterator<T>,然後將遍歷出的每一個元素傳遞到了action裏面的accept()方法中,下面打開Consumer<T>
//Consumer明顯就是一個函數是接口
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
四:Lambda方法的傳遞
前面說過,函數是編程語言,方法可以當作參數傳遞,javaLambda雖然沒能完全的實現這樣的邏輯,但是也有了基本的應用,下面介紹一下Lambda “::”進行方法的傳遞。
- 首先我們自定義一個符合Lambda的函數式接口
@FunctionalInterface
public interface MyLambda<T> {
public T accept(T t);
}
該接口有一個自定義的方法,這個方法返回一個T類型的數據。這個接口中的方法必須要被實現纔可以用,這個大家都知道的。其實並不需要你每一次都再用的時候去實現,你可以去別的方法裏面直接拿一個和accept方法特性一樣(特性一樣的意思是,你不能將沒有返回值的方法傳遞給有返回值的接口,飯之亦然)的方法直接將其當作accept來用,這就相當於將別的類的方法傳遞給了MyLambda<T>這個接口。話不多說,下面我們自定義一個Test類。
- class Test {
// static methods
static String startsWith(String s) {
return s;
}
public String endWith(String s) {
return s;
}
}
- 實現普通方法傳遞:
public class Main {
public static void main(String[] args) {
Test test = new Test();
//將test類中的方法當成accept方法的實現體,myLambda.accept()實際上調用的就是test中的//endwith方法
MyLambda<String> myLambda = test::endWith;
System.out.println( myLambda.accept( "lambda lambda lambda lambda" ) );
}
}
- 實現靜態方法的傳遞
public class Main {
public static void main(String[] args) {
MyLambda<String> myLambda = Test::startsWith;
System.out.println( myLambda.accept( "lambda lambda lambda lambda" ) );
}
}