一、簡介
java8于2014年釋出,相比于java7,java8新增了非常多的特性,如lambda表達式、函數式接口、方法引用、預設方法、新工具(編譯工具)、Stream API、Date Time API、Optional等 。 目前很多公司的老産品依然使用的java7,甚至開發人員開發新産品時依然沒有選擇更新, 寫關于java8系列文章的目的在于梳理和分享java8新增的主要特性,開發時也可以用作參考。
lambda表達式是java8新增的主要特性之一,lambda表達式又稱閉包或匿名函數,主要優點在于簡化代碼、增強代碼可讀性、并行操作集合等。至于是否使用,有的同學覺得不适應,有的同學欲罷不能,見仁見智~
技多不壓身,本文将采用由淺入深的方式,講解java8 lambda表達式的文法及使用,并附帶代碼進行示範。
二、lambda文法
lambda的基本文法:
(parameters) -> expression
or
(parameters) ->{ statements; }
lambda表達式的特性:
1、可選類型聲明: 無需聲明參數類型,編譯器即可自動識别
2、可選的參數圓括号: 僅有一個參數時圓括号可以省略
3、可選的大括号:主體隻包含一個語句時可省略大括号
4、可選的傳回關鍵字:主體隻包含一個表達式傳回值并省略大括号時,編譯器會自動return傳回值;有大括号時,需要顯式指定表達式return了一個數值
特性示例:
//1、無參數,傳回值1
() -> 1
//2、無參數,無傳回值
() -> System.out.print("Java8 lambda.");
//3、1個參數,參數類型為數字,傳回值為其值的5倍
x -> 5 * x
//4、2個參數,參數類型均為數字,傳回值為其內插補點
(x, y) -> x - y
//5、2個參數,指定參數類型均為int型,傳回值為其內插補點
(int x, int y) -> x - y
//6、1個參數,指定參數類型為String ,無傳回值
(String str) -> System.out.print(str)
三、java8 lambda使用示例
前面我們講到lambda表達式的文法和特性,那麼在java8中如何使用lambda表達式呢?我們先以用幾個示例來展現lambda表達式在java8中的使用。
3.1 java Runnable接口的lambda實作
用lambdah代替匿名類是java8中lambda的常用形式,本文以開發同學經常使用的Runnable接口匿名類為示例,示範如何用lambda表達式來代替匿名類:
在java8之前:
new Thread(new Runnable()
{
@Override
public void run()
{
System.out.println("No use lambda.");
}
}).start();
在java8之後:
可以看到,java8中利用lambda表達式大大簡化了代碼編寫。
此處簡要提下,用lambda表達式代替匿名類的關鍵在于,匿名類實作的接口使用了java.lang.FunctionalInterface注解,且隻有一個待實作的抽象接口方法,如Runnable接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
3.2 java List疊代的lambda實作
開發同學經常會使用到集合類,并對集合類對象進行疊代,以實作業務邏輯。
java8中,集合類的頂層接口java.lang.Iterable定義了一個forEach方法:
/* @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對象,Consumer類位于java.util.function包下,我們看下其定義:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
到此已經很容易聯想到,我們可以采用lambda表達式來實作java8集合的疊代邏輯,下面我們進行示例:
在java8之前:
List<Integer> features = Arrays.asList(1,2);
for (Integer feature : features) {
System.out.println(feature);
}
在java8之後:
List<Integer> features = Arrays.asList(1,2);
features.forEach(n -> System.out.println(n));
上述邏輯還可以用java8的方法引用來表示:
List<Integer> features = Arrays.asList(1,2);
features.forEach(System.out::println);
方法引用也是java8的新特性,由::操作符标示
四、函數式接口
在上一節中我們提到:“用lambda表達式代替匿名類的關鍵在于,匿名類實作的接口使用了java.lang.FunctionalInterface注解,且隻有一個待實作的抽象接口方法”, 這裡的接口便是函數式接口。
函數式接口(Functional Interface)是java8新增的特性,它是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。函數式接口可以被隐式轉換為lambda表達式。
Runnable接口是在JDK1.8之前已經存在的接口,在JDK1.8中加入了@FunctionalInterface注解,表示将其定義為一個函數式接口。在JDK1.8中定義的函數式接口還有:
ava.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
DK1.8新增加的函數式接口有java.util.function包下的接口,典型的如上一節中提到的Consumer接口,感興趣的讀者可以閱讀JDK1.8的源碼,在此不逐個列出,在下一節本文還會列舉java.util.function包中典型的函數式接口的使用。
到這裡,可以總結出,java8中用lambda表達式代替匿名内部類,本質上是将接口定義為函數式接口,并将函數式接口隐式轉換為lambda表達式、
五、典型函數式接口的使用
上一節我們了解了java8函數式接口的概念和定義方法,本節再列舉java.util.function幾個典型的函數式接口的使用,加深下函數式接口與lambda表達式結合的了解。
5.1 Predicate接口
5.1.1 Predicate接口的基本用法
Predicate接口适合用于過濾,測試對象是否符合某個條件,Predicate接口源碼如下:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
可以看到,Predicate接口待實作的唯一抽象方法是 boolean test(T t) 方法。我們用Predicate接口實作從整數型數組中過濾正數:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
filter(numbers, n -> n > 0);
}
public static void filter(List<Integer> numbers, Predicate<Integer> condition)
{
for (Integer number : numbers)
{
if (condition.test(number))
{
System.out.println("Eligible number: " + number);
}
}
}
運作結果如下:
Eligible number: 4
Eligible number: 5
對數組的疊代,還可以使用Stream API的方式:
public static void main(String[] args)
{
List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
numbers.stream().filter(n -> n > 0).forEach(n -> System.out.println("Eligible number: " + n));
}
上面的代碼采用Stream API + Predicate接口 + Consumer接口的方式實作了同樣的功能,代碼量大大減少。Stream API(java.util.stream)同樣是java8的新特性,将真正的函數式程式設計風格引入到java語言中,進一步簡化了代碼。