最近Kotlin看得挺爽,曾經比較Java和JavaScript,
遺憾過Java的函數太low,Kotlin在函數方面徹底彌補了Java的缺憾。
雖然java8支持了lambda表達式,可是仍是沒有kotlin爽。
今天只談函數和lambda,至於函數式編程,就不班門弄斧了。java
在java中彷佛並不怎麼說函數,而是說方法,方法是對象的行爲能力,那函數是什麼?git
高中的數學是這樣定義函數這個概念的:github
設A,B爲非空的數集,若是按照某種肯定的對應關係f,
使對於集合A中的任意的任意一個數x,在集合B中都有惟一肯定的數f(x)和它對應,
那麼就稱"f:A→B"爲從集合A到集合B的一個函數,記做:
y=f(x),x∈A
其中,x叫作自變量,x的取值範圍叫作函數的[定義域]
與x的值對應的y值叫作函數值,函數值的集合{f(x)|x∈A}叫作函數的[值域]
複製代碼
數學中一元函數的組成是兩個集合和一個對應法則,
每一個自變量在對應法則的映射下都能得到惟一因變量。 我更願意將數學中的函數看作對應法則下,自變量的因此變化集合
這貌似和編程中的函數是兩個概念,可是在思想上仍是有類似之處的:編程
若是將自變量看作輸入狀態,在對應法則之下,每一個輸入都對應着惟一對應的輸出狀態
而編程中的函數也是作相似的事:將輸入的材料數據經過邏輯處理,造成特定輸出,只是變化維度(參數)比較多
複製代碼
拿下面的函數來講,對於輸入x總能保持惟一的y輸出數組
fun fx(x: Int): Int {
val y = x + 2
return y
}
複製代碼
-- 也許你會說:"這TM不就是加個2嗎,須要講的這麼費勁?",
-- 我想說:"不要太糾結表象,我寫成val y = Math.sqrt(Math.exp(x) - 3 * Math.acos(x)) - Math.log(x)就會很高大上嗎?"
-- 在我眼中,這只是一種對應關係,它的本質和它的表示並無關係,就算寫成val y = 1
,它的本質並不會改變:
-- 還是對於輸入x總能保持惟一的y輸出,這就是抽象,太在乎表象就會膚淺以至視野的侷限。bash
Kotlin中的函數也是一種數據類型,其類型爲:
(形參類型,形參類型)->返回值類型
在Kotlin中使用::函數名
獲取一個函數的引用,函數是能夠做爲一個對象存在的微信
val line: (Double) -> Double
line = ::fx
line(8.0)//10.0
println(line)//fun fx(kotlin.Double): kotlin.Double
println(line is (Double) -> Double)//true
|-- 從效果上,普通視野來看就是讓入參+2,並無什麼了不得的
|-- 但從整個宏觀來看該函數實現了一個 y = x + 2 的線性數據轉換器,是否是高大上了一點
複製代碼
如今有一個gx,實現
y=e^x
的數據轉化器。app
fun gx(x: Double): Double {
val y = Math.exp(x)
return y
}
複製代碼
你也許能夠想到:既然函數能夠做爲對象,那麼也能夠當作入參
而後就一不當心拼出了下面這個看起來挺帥氣的函數,這時讓fx做爲入參
腳指頭想一想應該也知道是y = e^(x+2)
,這就實現了兩個函數的疊合。ide
fun gx(x: Double, f: (Double) -> Double): Double {
val y = Math.exp(f(x))
return y
}
println(gx(0.0, ::fx))//7.38905609893065
複製代碼
入參是函數,函數能夠寫成Lambda表達式,這裏gx的函數入參類型:
(Double) -> Double
對應的Lambda表達式形式爲:{ 參數名:Double -> 若干語句 最後一句返回Double}
,
而後下面圖形的數據轉換器就ok了,將自變量x經過sin轉換器後,再經過exp轉化器,也可獲得惟一的輸出函數式編程
|-- 使用匿名函數,不用Lambda
gx(5.0, fun(x: Double): Double {
return Math.sin(x)
})
|-- 使用已存在的函數,不用Lambda
gx(5.0, ::sin)
|-- 使用Lambda,標準型--------------------
gx(5.0, { x: Double ->
Math.sin(x)
})//0.3833049951722714
|-- Lambda特性:做爲最後一參可置後--------------
gx(5.0) { x: Double ->
Math.sin(x)
}//0.3833049951722714
|-- 可推導出變量類型,變量類型可省略------------------
gx(5.0) { x ->
Math.sin(x)
}//0.3833049951722714
|-- 只有一個參數時能夠用it代替,省略變量---------------
|-- 這樣一看是否是對Kotlin的Lambda語法有了些認識
gx(5.0) {Math.sin(it)}//0.3833049951722714
複製代碼
好了,Lambda的引入完成,也許你有點暈,不要緊,繼續看
val ints = IntArray(10) { it }//初始化 0 1 2 3 4 5 6 7 8 9
ints.map {
it * it
}.forEach { print("$it "); }//0 1 4 9 16 25 36 49 64 81
複製代碼
---->[_Arrays.kt#map]-----------------------
public inline fun <R> IntArray.map(transform: (Int) -> R): List<R> {
return mapTo(ArrayList<R>(size), transform)
}
|-- map函數的入參是 (Int) -> R 類型的函數,返回值是 List<R>
|-- 它調用了mapTo方法
---->[_Arrays.kt#mapTo]-----------------------
public inline fun <R, C : MutableCollection<in R>> IntArray.mapTo(destination: C, transform: (Int) -> R): C {
for (item in this)
destination.add(transform(item))
return destination
}
|-- 這方法頭有點長,仔細看看:方法入參 destination,類型C,其中C是MutableCollection類型的
|-- 從上面傳入的ArrayList<R>(size)來看,是一個size尺寸的空列表,第二參還是剛纔的函數transform
|-- 讓this的全部元素通過transform方法,而後加入到空列表裏,再將destination返回出去
|-- 這樣一看map方法也沒有想象中的那麼神奇,也能夠看出map並不會污染原數組
複製代碼
關於lambda表達式在Java中最多見的應數一個方法的接口,在stream流中即是屢見不鮮
List<Integer> ints = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> list = ints.stream()
.map((e) -> {
return e * e;
})
.collect(Collectors.toList());
|-- 簡寫形式
List<Integer> list = ints.stream()
.map(e -> e * e)
.collect(Collectors.toList());
---->[Java中的lambda表達式是什麼?]----------------
|-- 源碼:Stream#map------------
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
|-- 能夠看出入參是一個Function的類型,有兩個泛型 T 和 R
|-- 那Function對象又是什麼鬼?
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
|-- Functions是一個接口,有兩個泛型:T和R ,apply函數出入T類型參數,返回一個R 類型值
* @param <T> the type of the input to the function 輸入的類型
* @param <R> the type of the result of the function 輸出的類型
|-- 其中有 compose和andThen兩個默認的構造接口,看樣子compose能夠截胡,先走一波before的Function
|-- andThen相反,先走本身的apply,而後再走after的apply
|-- 打個比方,我有一塊糖,compose是吃了吐出來再給我吃,andThen是我吃了,吐出來給她吃
|-- 變量提取一下,能夠看出這裏是一個Function<Integer, Integer>的對象
Function<Integer, Integer> fn = e -> e * e;
fn.apply(8);//64
fn.compose((Integer e) -> {
System.out.println();
return e * 2;
}).apply(8)//256 = (8*2)^2
fn.andThen((Integer e) -> {
System.out.println();
return e * 2;
}).apply(8));//128 = 8*8 *2
複製代碼
相似,也是不會改變原數組
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let result = arr.map(e => {
return e * e;
});
console.log(arr);//[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
console.log(result);//[ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]
|-- 簡寫形式:
let result = arr.map(e => e * e);
複製代碼
Python的lambda表達式怎麼多行語句...還望指點,網上的都是一行...
arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
result = map(lambda e: {e * e}, arr)
print(arr)# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(result)) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
|-- 簡寫
result = map(lambda e: e * e, arr)
複製代碼
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var result = arr.map((e) => (e * e));
print(arr);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(result);//(0, 1, 4, 9, 16, 25, 36, 49, 64, 81)
|-- 簡寫
var result = arr.map((e) => e * e);
複製代碼
可見,每種語言對於lambda表達式的表示形式都有區別,
下面是各語言未簡寫的完整和簡寫的lambda表達式
|-- Kotlin
val fn = { e: Int -> {
e * e
}
}
簡寫:val fn = { e: Int -> e * e }
|-- Java
Function<Integer, Integer> fn = (Integer e) -> {
return e * e;
};
簡寫:Function<Integer, Integer> fn = e -> e * e;
|-- JavaScript
let fn = (e) => {
return e * e
};
簡寫:let fn = (e) => e * e;
|-- Python
fn = lambda e: {
e * e
}
簡寫:fn = lambda e: e * e
|-- Dart
var fn = (e) => (
e * e
);
簡寫:var fn = (e) => e * e;
複製代碼
lambda表達式只是函數的一種特別的書寫格式,它自己仍是函數,能夠賦給變量以及調用
|-- 加法函數
fun add(x: Int, y: Int): Int {
return x + y
}
|-- 轉化爲lambda表達式
val add = { x: Int, y: Int -> { x + y } }
簡寫:val add = { x: Int, y: Int -> x + y }
|-- 能夠將lambda表達式當作普通的函數來調用
add(3, 5)//8
|-- 再看傳入一個函數如參的add方法,它在加以前先對x,y進行處理
fun add(x: Int, y: Int, fn: (Int) -> Int): Int {
return fn(x) + fn(y)
}
|-- 這樣就能夠計算x,y的平方和:(-3)^2+4^2=25
val result = add(-3, 4) { e -> e * e }
|-- 這樣就能夠計算x,y的絕對值和:|-3|+|4| = 7
val result = add(-3, 4) { e -> Math.abs(e) }
|-- 好處不言而喻,能夠自定義拓展用法,應你所需
|-- 固然若是你以爲麻煩,就像加一下而已,也能夠設個默認值
fun add(x: Int, y: Int, fn: (Int) -> Int = { e -> e }): Int {
return fn(x) + fn(y)
}
val result = add(-3, 4) //1
複製代碼
Java中並不像當代語言那麼隨性,由上面的Function也能夠看出,
是接口讓Java支持lambda表達式的,既然Java有Function接口,咱們固然也能夠自定義
---->[定義方法接口]------------------
public interface AddFun<T, R> {
R apply(T x, T y);
}
|-- 使用--------------------
AddFun<Integer, Integer> add = (x, y) -> x + y;//加法的lambda表達式
Integer result = add.apply(4, 5);
|-- 如何向上面那樣自定義拓展加法呢?
|-- 也就是再加一個(函數)入參,能夠傳入lambda表達式
public interface AddFun<T, R> {
R apply(T x, T y, Function<? super T, ? extends R> rule);
}
AddFun<Integer, Integer> add = (x, y, rule) -> rule.apply(x) + rule.apply(y);//加法的lambda表達式
Integer result = add.apply(3, 4, e -> e * e);//25
Integer result = add.apply(-3, 4, e -> Math.abs(e));//7
Integer result = add.apply(-3, 4, Math::abs);//7 簡寫
複製代碼
|-- 加法函數寫成lambda表達式
let la = (x, y) => x + y;
console.log(la(3, 4));//7
|-- 加法 + lambda表達式入參
function add(x, y, fn = e => e) {
return fn(x) + fn(y);
}
let a = add(-3, 4, e => e * e);
let b = add(-3, 4, e => Math.abs(e));
console.log(a);//25
console.log(b);//7
|-- 合在一塊兒寫也能夠
let la = (x, y, fn) => fn(x) + fn(y);
la(-3, 4,e => e * e);//25
複製代碼
套路都差很少,就不廢話了
|-- Python
add = lambda x, y: x + y
addex = lambda x, y, fn: fn(x) + fn(y)
a = add(3, 4)
b = addex(-3, -4, lambda e: e * e)
print(a)#7
print(b)#25
|-- Dart
var add = (x, y)=> x + y;
var addex = (x, y, fn) => fn(x) + fn(y);
var a = add(3, 4);
var b = addex(-3, -4, (e)=> e * e);
print(a);//7
print(b);//25
複製代碼
Java的stream流對集羣元素的操做,Kotlin對集羣元素的操做,傳入函數,使用lambda表達式很方便
另外JavaScript,Python,Dart操做集羣時或多或少都會涉及這些forEach,map,all,any,reduce等。
|-- forEach操做:遍歷元素
ints.stream().forEach(e->{
System.out.println(e);
});
|-- allMatch操做:根據條件控制遍歷,看是否所有符合條件,只要有一個不合格,中斷遍歷並返回false
List<Integer> ints = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
Stream<Integer> stream = ints.stream();
boolean b = stream.allMatch(e -> {
System.out.println(e);
return e < 5; //0 1 2 3 4 5
});
System.out.println(b);//false 返回是否所有都符合要求
|-- anyMatch操做:根據條件控制遍歷,看是否有符合條件,只要有一個合格,中斷遍歷並返回true
boolean has = ints.stream().anyMatch(e -> {
System.out.println(e);
return e > 5; //0 1 2 3 4 5 6
});
System.out.println(has);//true
|-- noneMatch操做:根據條件控制遍歷,看是否有符合條件,只要有一個合格,中斷遍歷並返回false
boolean hasNot = ints.stream().noneMatch(e -> {
System.out.println(e);//0 1 2 3 4 5 6
return e >5 ;
});
System.out.println(hasNot);//false
|-- filter操做:過濾出須要的元素,返回的還是stream,因此能夠連續使用
ints.stream().filter(e -> e % 2 == 0)
.forEach(System.out::println);//0 2 4 6 8
|-- map操做:能夠將全部的元素按照規則全體變化,返回的還是stream
|-- collect操做:將一個stream變成Collector,容器對象
List<Integer> list = ints.stream()
.map(e -> e * e)
.collect(Collectors.toList());
System.out.println(list);
|-- flatMap操做:將層級結構扁平化。好比有三個小偷,每一個人偷了幾個東西(集合元素)
|-- 而後三我的被警察逮到了,三我的一次將本身偷得東西一個一個擺在桌子上,ok,這就是flatMap
List<Integer> int0to4 = Arrays.asList(0, 1, 2);
List<Integer> int3o7 = Arrays.asList(3, 4);
List<Integer> int4to8 = Arrays.asList(4, 5);
Stream.of(int0to4, int3o7, int4to8).flatMap(list -> list.stream())
.forEach(System.out::println);//0 1 2 3 4 4 5
|-- limit操做:截取前n個元素,返回的還是stream
|-- skip操做:跳過前n個元素,返回的還是stream
ints.stream()
.limit(6)//截取6個 0,1,2,3,4,5
.skip(2)//跳過前兩個
.forEach(System.out::println);//2 3 4 5
|-- findFirst:獲取流中的第一個元素
int str = ints.stream()
.filter(x->x<-3)//過濾流
.findFirst()//第一個
.orElse(10000);//默認值
System.out.println(str);//4
|-- mapToInt:造成int流,好處在於有額外的API
IntSummaryStatistics stats = ints.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("max : " + stats.getMax());//9
System.out.println("min : " + stats.getMin());//0
System.out.println("sum : " + stats.getSum());//45
System.out.println("ave : " + stats.getAverage());//4.5
System.out.println("count : " + stats.getCount());//10
|-- max和min操做,二者相反,傳入一個比較器,返回一個Optional對象
int max = ints.stream().max((o1, o2) -> o1 - o2).get();
int min = ints.stream().min((o1, o2) -> o1 - o2).get();
System.out.println(max+"--"+min);//9--0
|-- reduce操做:
Integer reduce = ints.stream().reduce(0, (result, value) -> {
System.out.println(result + "---" + value);
return result + value;
});
System.out.println(reduce);
感受reduce超有意思:感受的話像貪吃蛇,一個一個吃,但吃下一個以前,吃前一個的效果還在
其中第一參是偏移量,能夠當作貪吃蛇得初始狀況,在此基礎上,每遍歷一次,吃一個
0---0 4---0
0---1 4---1
1---2 5---2
3---3 7---3
初始值0 6---4 初始值4 10---4
10---5 14---5
15---6 19---6
21---7 25---7
28---8 32---8
36---9 40---9
45 49
複製代碼
|-- forEach操做:遍歷元素
ints.forEach {
print("$it ")//0 1 2 3 4 5 6 7 8 9
}
|-- all操做:根據條件控制遍歷,看是否所有符合條件,只要有一個不合格,中斷遍歷並返回false
val b = ints.all {
println(it);
it < 5; //0 1 2 3 4 5
}
println(b) //false
|-- any操做:根據條件控制遍歷,看是否有符合條件,只要有一個合格,中斷遍歷並返回true
val any = ints.any {
println(it);
it > 5;////0 1 2 3 4 5 6
}
println(any)//true
|-- noneMatch操做:根據條件控制遍歷,看是否有符合條件,只要有一個合格,中斷遍歷並返回false
val any = ints.none() {
println(it);
it > 5;//0 1 2 3 4 5 6
}
println(any)//false
|-- filter操做:過濾出須要的元素,不損壞原數組
ints.filter {
it % 2 == 0
}.forEach { print("$it "); }//0 2 4 6 8
|-- map操做:能夠將全部的元素按照規則全體變化,返回的還是stream
ints.map {
it * it
}.forEach { print("$it "); }//0 1 4 9 16 25 36 49 64 81
|-- dropWhile操做:知道知足條件以前的元素都刪除
val list = ints.dropWhile { it < 6 }
println(list)//[6, 7, 8, 9]
|-- reduce操做:
val reduce = ints.reduce { result: Int, value: Int ->
println("$result --- $value")
result + value
}
println(reduce)
複製代碼
最後總結一句:在Java中的lambda表達式表示一個接口對象,在各現代語言表示函數
var la={x: Int ,y:Int-> x +y}
println(la is (Int, Int) -> Int)//true
println(::add is (Int, Int) ->Int)//true
fun add(x: Int, y: Int): Int {
return x + y
}
複製代碼
關於各語言認識深淺不一,若有錯誤,歡迎批評指正。
項目源碼 | 日期 | 附錄 |
---|---|---|
V0.1--無 | 2018-3-6 | 無 |
發佈名:
從五大語言看函數和lambda表達式
捷文連接:https://juejin.im/post/5c7a9595f265da2db66df32c
筆名 | 微信 | |
---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 |
個人github:https://github.com/toly1994328
個人簡書:https://www.jianshu.com/u/e4e52c116681
個人簡書:https://www.jianshu.com/u/e4e52c116681
我的網站:http://www.toly1994.com
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持