Java8新特性之lambda表示式
在瞭解lambda表示式之前,必須先了解函數語言程式設計、函式式介面和default方法。在Java8出來之前,別的程式語言中已經有了函數語言程式設計這種概念,只不過後來沒落了,不過在最近幾年又火起來了。在Java8中大力提倡我們使用函數語言程式設計,並且更新了一些類(java.utl.function),讓我們根據這些類來程式設計。
一、有關函數語言程式設計、函式式介面和default方法的介紹
1.什麼是函數語言程式設計?
函數語言程式設計是種程式設計正規化,與之相對應的是指令式程式設計,下面舉個例子來對比一下,比如計算(1+1)*2-3的結果:
在指令式程式設計中可以這樣寫: int a = (1+1)*2-3;為了體現命令式和函式式的區別,用以下的方式方便理解:
int a = 1 + 1 ;
int b = a * 2 ;
int c = b - 3 ;
函數語言程式設計:
subtract(multiply(add(1,1),2),3);
通過上面這個例子可以看出,函數語言程式設計大量使用的是函式+表示式,不使用語句,因此函數語言程式設計的程式碼會看起來很精簡。
2.什麼是函式式介面?
函數語言程式設計是一個很早就提出來的概念,不過函式式介面是Java8中新增的,那什麼是函式式介面呢?在一個介面中只有一個抽象方法的介面被稱為函式式介面,比如說java.lang.Runnable、java.util.concurrent.Callable。對比看這兩個介面可以發現,介面定以前都有一個@FunctionalInterface
自定義一個函式式介面:
/**
* 函式式介面
*/
@FunctionalInterface
public interface MyInterface {
void method01(); //修飾符預設為public abstract,省略不寫
}
3.default方法又是什麼?
這個方法也是Java8中新增的方法,在jdk8之前,介面中的方法必須都是抽象的,在jdk8中允許介面中定義非抽象方法,在介面中的非抽象方法使用default修飾即可,比如在jdk8中新增一個函式式介面:java.util.function。Java8中打破了介面中的方法必須都是抽象這一規範,有一個好處就是可以提高程式的相容性,在java.lang.Iterable介面中新增了foreach方法,該方法就是使用default修飾的。
在介面中定義default方法可以變相的讓Java支援“多繼承”。
下面程式碼是jdk8中java.lang.Iterable的原始碼(一共有3個方法, 除了iterator(),另外兩個方法都是被default修飾的):
package java.lang;
import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
/**
* Implementing this interface allows an object to be the target of
* the "for-each loop" statement. See
* <strong>
* <a href="{@docRoot}/../technotes/guides/language/foreach.html">For-each Loop</a>
* </strong>
*
* @param <T> the type of elements returned by the iterator
*
* @since 1.5
* @jls 14.14.2 The enhanced for statement
*/
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);
}
}
/**
* Creates a {@link Spliterator} over the elements described by this
* {@code Iterable}.
*
* @implSpec
* The default implementation creates an
* <em><a href="Spliterator.html#binding">early-binding</a></em>
* spliterator from the iterable's {@code Iterator}. The spliterator
* inherits the <em>fail-fast</em> properties of the iterable's iterator.
*
* @implNote
* The default implementation should usually be overridden. The
* spliterator returned by the default implementation has poor splitting
* capabilities, is unsized, and does not report any spliterator
* characteristics. Implementing classes can nearly always provide a
* better implementation.
*
* @return a {@code Spliterator} over the elements described by this
* {@code Iterable}.
* @since 1.8
*/
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
自定義一個由default方法的介面:
@FunctionalInterface
public interface MyInterface {
double method01(double a, double b); //抽象方法
default String method02() { //default方法
return "hello";
}
default int method03(int a) { //default方法
return a*a;
}
}
二、lambda表示式的介紹
1.lambda表示式是啥?
可以將lambda表示式看作是一個匿名方法,lambda表示式只能用於函式式介面。主要由()->{}這些符號組成,()裡放的是引數(列表),{}裡放的是程式碼塊(和返回值)。使用lambda表示式可以編寫出比匿名內部類更簡潔的程式碼。
使用lambda表示式的例子(用到了上面的例子中函式式介面MyInterface):
public class TestLambda01 {
public static void main(String[] args) {
//不使用lambda表示式,使用匿名內部類建立MyInterface的物件
MyInterface mi1 = new MyInterface() {
@Override
public double method01(double a, double b) {
System.out.println("no lambda:");
return a+b;
}
};
//使用lambda表示式(介面一定要是函式式介面)
MyInterface mi2 = (double a, double b) -> {
System.out.println("lambda:");
return a+b;
};
//使用lambda表示式,省略引數型別(編譯器都可以從上下文環境中推斷出lambda表示式的引數型別,因此可以省略引數型別)
MyInterface mi3 = (a, b) -> {
System.out.println("lambda:");
return a+b;
};
//使用lambda表示式,省略return關鍵字(程式碼塊只有一行程式碼時,{}可以省略)
MyInterface mi4 = (a, b) -> a+b;
}
}
使用lambda表示式建立執行緒的例子:
public class TestLambda02 {
public static void main(String[] args) {
//不使用lambda表示式建立執行緒
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("no lambda");
}
}).start();
//使用lambda表示式
//因為Thread類中接收Runnable型別的物件,所以編譯器會識別出lambda表示式是Runnable物件
new Thread(() -> System.out.println("lambda")).start();
}
}
lambda表示式為什麼只能用於函式式介面中?
lambda表示式實際上就是重寫了介面中的抽象方法,在函式式介面中只有一個抽象方法,此時編譯器會認定重寫的就是該唯一的抽象方法。倘若一個介面中有多個抽象方法,而lambda表示式是沒有匿名的,編譯器就無法判斷其重寫的是哪個抽象方法了。
2. forEach方法
在jdk8中的java.lang.Iterable介面中新增了非抽象的forEach方法,可以使用該方法配合lambda表示式來遍歷集合。
// 使用forEach方法+lambda表示式遍歷集合
public class TestLambda03 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(6);
list.add(3);
list.add(8);
list.add(2);
list.add(7);
list.add(5);
list.add(8);
//以前遍歷集合的一種方式
for (Integer i:list) {
System.out.println(i);
}
//使用forEach方法+lambda表示式遍歷集合
//list.forEach((i) -> System.out.println(i));
//只有一個引數時,可以省略()
list.forEach(i -> System.out.println(i));
}
}
以上使用lambda表示式之後已經夠簡潔,但是上述的最後一條語句還可以簡介,當lambda表示式只有一行程式碼時,可以使用方法的引用,上述可以寫成:
list.forEach(System.out :: println);
3.方法引用
方法引用主要是用來簡寫lambda表示式,有些lambda表示式裡面僅僅是執行一個方法呼叫,這時使用方法引用可以簡寫lambda表示式。
四種類型的方法引用: ①靜態方法引用,類名::方法名 ②某個物件的引用,物件變數名::方法名 ③特定類的任意物件的方法引用,類名::方法名 ④構造方法,類名::new
前三種比較好理解,就舉一個靜態方法引用的例子:
public class TestLambda04 {
public static void main(String[] args) {
Integer[] arr = {4,8,2,9,6,5,1}; //對該陣列排序
//不使用lambda表示式
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer x, Integer y) {
return Integer.compare(x,y);
}
});
//lambda表示式
Arrays.sort(arr,(x,y) -> Integer.compare(x,y));
//上述只有 Integer.compare(x,y)一條語句,所以可以使用方法的引用,改為:
Arrays.sort(arr,Integer :: compare);
}
}
構造方法引用例子:
import java.util.function.Supplier;
public class Car {
public static Car buy(Supplier<Car> s) {
return s.get(); //通過get方法獲取傳入的Car型別的物件
}
}
// 構造方法引用
public class Testlambda05 {
public static void main(String[] args) {
Car car = Car.buy(Car::new);
}
}