函數(shù)式接口使用背景
我們知道赋续,java是一門面向?qū)ο缶幊陶Z言男翰,java中一切都是面向?qū)ο蟮模ǔ嗽紨?shù)據(jù)類型)。在java中函數(shù)(方法)是類/對象的一部分纽乱,不能單獨(dú)存在蛾绎。而其他一些函數(shù)式編程語言如C++、Javascript等語言鸦列,可以編寫單獨(dú)的函數(shù)并可以直接調(diào)用它們租冠。
面向?qū)ο蟛⒎遣缓茫皇怯袝r(shí)候需要編寫冗長的代碼薯嗤。舉個(gè)簡單的例子顽爹,我們需要創(chuàng)建一個(gè)Runnable實(shí)例,通常我們會使用匿名內(nèi)部類如下:
Runnable r = new Runnable(){
@Override
public void run() {
System.out.println("My Runnable");
}};
其實(shí)在這段代碼中骆姐,真實(shí)有用的僅僅只是內(nèi)部的run方法镜粤,其他的代碼只是java面向?qū)ο笠蟮摹?/p>
java8函數(shù)式接口和lambda表達(dá)式可以讓我們編寫少量代碼就能達(dá)到上述效果。
java8函數(shù)式接口
在java8中玻褪,本身只有一個(gè)抽象方法的接口即可稱之為函數(shù)式接口肉渴,可以使用@FunctionalInterface注解顯示標(biāo)明接口是函數(shù)式接口。這個(gè)注解并非必須的带射,如果加上該注解黄虱,則接口若存在多于一個(gè)的抽象方法則會提示編譯錯(cuò)誤。
java8函數(shù)式接口的最大好處是可以使用lambda表達(dá)式來初始化函數(shù)式接口從而避免匿名內(nèi)部類樣式的笨重寫法庸诱。
java8的集合API已經(jīng)重寫了捻浦,并且引進(jìn)了使用很多的函數(shù)式接口的新的流式API晤揣。在java.util.function包下定義了很多函數(shù)式接口如:Consumer、Supplier朱灿、Function 和Predicate昧识。
lambda表達(dá)式
通過lambda表達(dá)式我們可以將函數(shù)式編程在java的面向?qū)ο笾行蜗蠡?br> 對象是java語言的基本,我們不可能離開對象單獨(dú)去使用方法盗扒,這也是為什么java提供lambda表達(dá)式僅僅能使用函數(shù)式接口的原因跪楞。
如果只有一個(gè)抽象方法,那么使用lambda表達(dá)式就不會存在困惑了侣灶。
lambda表達(dá)式的簽名:
*** (argument1, argument2,...) -> (body) ***
** (argument1, argument2,...)**表示方法簽名甸祭,argument1, argument2,...是參數(shù)列表
** -> ** 是箭頭,指向方法體
**(body) ** 是方法體褥影,可以使用{}包裹代碼塊來表示
如果是無參方法池户,則方法簽名可以使用 ()
如果只有一個(gè)參數(shù)的話,()可以省略
前面創(chuàng)建Runnable實(shí)例的代碼可以使用lambda表達(dá)式實(shí)現(xiàn):
Runnable r1 = () -> System.out.println("My Runnable");
解釋下這段代碼:
Runnable 是一個(gè)函數(shù)式接口凡怎,所以我們可以使用lambda表達(dá)式創(chuàng)建它的實(shí)例
因?yàn)?run()方法咩有參數(shù)校焦,所以我們的lambda表達(dá)式也沒有參數(shù)
就像if-else語句一樣,如果只有一行代碼的話我們可以省去{}符號了统倒。
為什么要使用lambda表達(dá)式
減少代碼量
使用匿名內(nèi)部類和lambda表達(dá)式的代碼量區(qū)分已經(jīng)很明顯了
支持連續(xù)地寨典、并行地執(zhí)行
lambda的另外一個(gè)好處就是我們可以使用流式API連續(xù)并行地執(zhí)行程序。
為了說明這點(diǎn)房匆,我們舉個(gè)例子耸成。判斷一個(gè)數(shù)是不是質(zhì)數(shù):
這段代碼不是最優(yōu)的,但是可以達(dá)到目的:
private static boolean isPrime(int number) {
if(number < 2) return false;
for(int i=2; i<number; i++){
if(number % i == 0) return false;
}
return true;
}
解決這個(gè)問題的代碼是連續(xù)的浴鸿,如果給定的數(shù)字很大的話很耗時(shí)井氢。另外一個(gè)缺陷是分支返回太多可讀性不好。使用lambda和流式API的寫法:
private static boolean isPrime(int number) {
return number > 1
&& IntStream.range(2, number).noneMatch(
index -> number % index == 0);
}
**IntStream **是一個(gè)自然排好序的元素為原始類型int的支持連續(xù)并行執(zhí)行的流赚楚。為了更好閱讀,代碼可以進(jìn)一步優(yōu)化為:
private static boolean isPrime(int number) {
IntPredicate isDivisible = index -> number % index == 0;
return number > 1
&& IntStream.range(2, number).noneMatch(
isDivisible);
}
range(arg1,arg2)方法返回一個(gè)**IntStream **包含arg1骗卜,但是不包含arg2的步長為1的序列宠页。
noneMatch()返回是否沒有元素匹配不上給定的預(yù)定義條件Predicate。
給方法傳遞行為action
我們需要對一個(gè)list中滿足某個(gè)條件的元素進(jìn)行求和寇仓。
public static int sumWithCondition(List<Integer> numbers, Predicate<Integer> predicate) {
return numbers.parallelStream()
.filter(predicate)
.mapToInt(i -> i)
.sum();
}
使用方法如下:
//對所有元素求和
sumWithCondition(numbers, n -> true)
//對是偶數(shù)的元素求和
sumWithCondition(numbers, i -> i%2==0)
//對所有大于5的元素求和
sumWithCondition(numbers, i -> i>5)
高效率的懶加載
如果我們需要找出3-11之間的最大的奇數(shù)举户,并求出它的平方。
我們可能使用這樣的代碼:
private static int findSquareOfMaxOdd(List<Integer> numbers) {
int max = 0;
for (int i : numbers) {
if (i % 2 != 0 && i > 3 && i < 11 && i > max) {
max = i;
}
}
return max * max;
}
上述代碼是在一個(gè)序列中處理我們可以使用流式API代替:
public static int findSquareOfMaxOdd(List<Integer> numbers) {
return numbers.stream()
.filter(NumberTest::isOdd)
.filter(NumberTest::isGreaterThan3)
.filter(NumberTest::isLessThan11)
.max(Comparator.naturalOrder())
.map(i -> i * i)
.get();
}
public static boolean isOdd(int i) {
return i % 2 != 0;
}
public static boolean isGreaterThan3(int i){
return i > 3;
}
public static boolean isLessThan11(int i){
return i < 11;
}
冒號表達(dá)式是方法的引用遍烦,NumberTest::isOdd 是 (i) -> isOdd(i) 或者
i -> NumberTest.isOdd (i) 的縮寫俭嘁。
更多的lambda表達(dá)式示例
() -> {} // 無參無方法體
() -> 42 // 無參有方法體
() -> null // 無參有方法體
() -> { return 42; } // 無參,代碼塊中返回結(jié)果
() -> { System.gc(); } //
// 復(fù)雜的代碼塊
() -> {
if (true) return 10;
else {
int result = 15;
for (int i = 1; i < 10; i++)
result *= i;
return result;
}
}
(int x) -> x+1 // 單個(gè)的聲明類型的參數(shù)
(int x) -> { return x+1; } //
(x) -> x+1 // 單個(gè)參數(shù)服猪,單條代碼同上
x -> x+1 // 同上
(String s) -> s.length() //
(Thread t) -> { t.start(); } //
s -> s.length() // 單個(gè)的編譯器可以推斷的類型參數(shù)
t -> { t.start(); } // 單個(gè)的編譯器可以推斷的類型參數(shù)
(int x, int y) -> x+y // 多個(gè)的聲明類型的參數(shù)
(x,y) -> x+y // 多個(gè)的可以推斷的類型參數(shù)
(x, final y) -> x+y // 錯(cuò)誤供填。不能修改final變量y
(x, int y) -> x+y // 錯(cuò)誤拐云,無法推斷混合類型
方法、構(gòu)造器引用
java8可以使用冒號表達(dá)式來引用方法:
System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new