@[java8新特性]
java8新特性之Lambda表達式
java8新特性包括lambda表示式,Stream流、Optional和全新的日期時間API,其中最好用的必須是lambda表達式和操作各種資料的Stream流了,本章節主要介紹Lambda表達式的使用
lambda表達式的使用
lambda表達式其實是一個匿名函數,我們可以将表達式了解為一段可以當做參數進行傳遞的代碼,通過lambda表達式,可以将java程式變得更加簡潔和靈活。
來看一段程式
@Test
public void test() {
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
};
Set<Integer> set = new TreeSet<>(comparator);
set.add(3);
set.add(2);
set.add(1);
System.out.println(set);
}
在java8之前,我們想實作對TreeSet的自定義排序,我們可以建立匿名的比較器類将其傳入TreeSet構造方法中,然而在java8之後,使用lambda表達式可以簡潔匿名内部類的代碼,如下:
@Test
public void test() {
Comparator<Integer> comparator = (o1, o2) -> {o2 - o1};
Set<Integer> set = new TreeSet<>(comparator);
set.add(3);
set.add(2);
set.add(1);
System.out.println(set);
}
原本的匿名内部類被替換成為了有->的一行代碼。
Lambda表達式的文法
java8中引入一個‘ -> ’操作符,被稱之為lambda操作符。該操作符将表達式拆分為左右兩個部分,左邊的部分為表達式的參數清單(使用括号),右邊的部分為方法體使用{}包裹方法的具體實作。
基于接口中方法聲明的不同,Lambda表達式的編寫方式也有有所不同。大體可以分為以下幾個方面:
·無參無傳回值:Runnable runnable = ()->System.out.println("Hello World")
·有一個參數無傳回值:Consumer consumer = (x) -> System.out.println(x)
若是方法隻有一個參數,則可以省略小括号不寫
Consumer consumer = x->System.out.println(x)
·有多個參數有傳回值,且lambda體中有多條語句:
Compartor<Integer> compartor = (o1,o2)->{
System.out.println("從小到大排列");
return o1 - o2;
}
**若是lambda體中有多條語句,則lambda體必須使用大括号包裹起來,若是隻有一條語句,則可以省略大括号。**
·有多個參數有傳回值,但是lambda體中隻有一條語句:
Compatar compatar = (o1,o2) -> o1-o2;
對于以上這種情況,lambda體中隻有一條傳回語句,則return可以省略不寫。
我們發現在所有的lambda表達式中,均沒有聲明參數的類型。這是因為JVM編譯器可以根據上下文自動推理出參數的類型。
需要注意的是,并不是所有的接口實作都可以使用Lambda表達式,它需要 函數型接口 的支援。
函數型接口
隻含有一個抽象方法的接口被稱為函數型接口
假設接口中存在多個抽象方法,那麼Lambda表達式根本無法知道我們到底是需要實作哪個抽象方法,是以Lambda表達式的使用必須是基于函數型接口。
在JDK1.8中,為此專門提供了一個注解@FuncationalInterface來聲明一個函數型接口,倘若在一個接口上使用了@FuncationalInterface注解,那麼該接口中隻能包含一個抽象方法。若是不滿足要求編譯器則會報錯。
@FunctionalInterface
public interface TestFunctionalInterface {
void method();
}
有些同學可能會發現在使用Lambda表達式實作一些功能時,還需要自己去額外編寫一個函數式接口,而事實上,JDK1.8已經為我們内置了四大核心函數式接口,分别是:
1、Consumer:消費型接口,抽象方法為:void accept(T t);
2、Supplier:供給型接口,抽象方法為:T get();
3、Function<T,R>:函數型接口,抽象方法為:R apply(T t);
4、Rredicate:斷言型接口,抽象方法為:boolean test(T t);
通過它們就已經能解決大部分的問題了,具體使用哪個接口可以根據自己的實際需求決定,比如若是需要實作的功能帶參數而無傳回值,則應該使用消費型接口。再比如若是要實作的功能無參數但是有傳回值,則需要使用供給型接口。
下面是一個供給型接口的例子:
public class Test1 {
public static void main(String[] args) {
Test1 test1 = new Test1();
List<Integer> list = test1.getList(5, () -> new Random().nextInt(20));
list.stream()
.forEach(System.out::println);
}
public List<Integer> getList(int length, Supplier<Integer> supplier) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < length; i++) {
list.add(supplier.get());
}
return list;
}
}
再來一個消費型接口的例子:
public class ConsumerTest {
public static void main(String[] args) {
ConsumerTest consumerTest = new ConsumerTest();
consumerTest.testConsumer((x) -> System.out.print(x));
}
public void testConsumer(Consumer<String> consumerTest) {
consumerTest.accept("測試消費型接口");
}
}
方法引用
若是Lambda體中的内容已經有方法實作了,那麼就可以使用方法引用,可以認為方法引用是Lambda體的另一種表達形式。
方法引用主要有以下幾種方式:
1、對象::執行個體方法名
2、類::靜态方法名
3、類::執行個體方法名
相關示例如下:
/**
* 測試對象的執行個體方法引用
*/
public void test1() {
Consumer<String> consumer = (x) -> System.out.println(x);
consumer.accept("測試對象方法引用");
Consumer<String> consumer1 = System.out::println;
consumer1.accept("測試對象方法應用");
}
/**
* 測試類的靜态方法引用
*/
public void test2() {
Comparator<Integer> comparator = (o1, o2) -> o1 - o2;
int compare = comparator.compare(1, 2);
System.out.println(compare);
Comparator<Integer> comparator1 = Integer::compare;
int compare1 = comparator1.compare(1, 2);
System.out.println(compare1);
}
/**
* 測試類的執行個體方法
*/
public void test3() {
BiPredicate<String,String> result = (s1 ,s2) -> s1.equals(s2);
System.out.println(result.test("abc", "abc"));
User user = new User();
user.setName("abc");
BiPredicate<User, String> result2 = User::compareName;
boolean abc = result2.test(user, "abc");
System.out.println(abc);
}
需要注意的是:
使用對象的執行個體方法和類的靜态方法引用時,需保證被引用的方法與使用的函數型接口的傳回值和形參一緻。
使用類的執行個體方法時,需要保證第一個參數為方法的調用者,第二個參數為方法的實參。當存在多個參數時,則需要根據情況編寫自定義的函數型接口和在相應的類中編寫方法的實作。
構造器引用
與方法引用類似,它通過類名::new 實作,我們知道一個類可以有多個重載的構造器,那麼構造器引用是怎麼知道我們需要調用哪個構造器的呢?同樣,還是根據我們使用的函數型接口的參數和傳回值進行判斷我們需要調用哪個構造器。下面是測試舉例:
public class ConstructorRefTest {
public static void main(String[] args) {
ConstructorRefTest constructorRefTest = new ConstructorRefTest();
constructorRefTest.test1();
constructorRefTest.test2();
}
public void test1() {
Supplier<User> supplier = () -> new User();
Supplier<User> supplier1 = User::new;
User user = supplier.get();
User user1 = supplier1.get();
System.out.println(user.name);
System.out.println(user1.name);
}
public void test2() {
Function<String, User> function = User::new;
User user = function.apply("測試構造器引用");
System.out.println(user.name);
}
class User {
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
}
}
Lambda表達式的使用就到這裡了,Lambda主要與函數型接口搭配使用,當沒有合适的函數型接口時,需要自定義一些滿足需求的函數型接口達到目的。
Lambda表達式的使用就介紹到這裡了,下一篇文章将詳細介紹JDK1.8中Stream流的使用。