Overview
本章主要介紹各語言中的方法定義以及與方法有很大聯(lián)系的 Lambda 表達式,閉包等概念缭保。
基本概念
為了更好的進行闡述,序章部分會簡單介紹一下本章會涉及到的各種名詞蝙茶,在接下來各語言中會再進一步解說艺骂。
方法,函數(shù)與過程
這三種名詞在編程中非常常見隆夯,其概念非常相近钳恕。一般來說函數(shù) (Function) 是可重復調(diào)用帶有有輸出和輸入的代碼塊。方法 (Method) 是定義在類中蹄衷,作為類的成員的函數(shù)忧额。過程(Subroutine)即沒有返回值的函數(shù)。也就是說函數(shù)是基礎形式愧口,方法與過程只是函數(shù)的特例睦番。
由于這些名詞容易混淆,在 Java 中一般都統(tǒng)一使用方法這個名詞。而在 Kotlin 中使用關(guān)鍵字 fun
即表示 Kotlin 中使用的其實是函數(shù)這個名詞托嚣。不過為了方便起見巩检,本系列主要都使用方法這個不一定精確的名字。
Lambda 表達式
Java 8 中引入了 Lambda 表達式示启,實際接觸時發(fā)現(xiàn)有不少同學把這和函數(shù)式算子混到了一起理解兢哭,覺得 Lambda 表達式遍歷效率不行,這是一個非常大的誤解夫嗓。實際上 Lambda 表達式不是什么新東西迟螺,就是一個匿名函數(shù)的語法糖,簡單理解就是繁體字=匿名函數(shù)舍咖,簡體字=Lambda煮仇,Java 8 無非就是在原來只能用繁字體的地方也允許使用簡體字罷了。
函數(shù)式接口
只包含一個抽象方法的接口谎仲,是 Java 8 中用于實現(xiàn) Lambda 表達式的根本機制浙垫,函數(shù)接口就是一種 SAM 類型。
SAM 類型
SAM (Single Abstract Method)是有且僅有一個抽象方法的類型郑诺,該類型可以是抽象類也可以是接口夹姥。
閉包
閉包是一種帶有自由變量的代碼塊,其最顯著的特性就是能夠擴大局部變量的生命周期辙诞。
閉包與方法
閉包和方法的最大區(qū)別是方法執(zhí)行完畢后其內(nèi)部的變量便會被釋放辙售,而閉包不會。閉包可以進行嵌套飞涂,而方法不行旦部。
Java 篇
方法
定義方法
語法為
[訪問控制符] [static] [返回值類型] 方法名(參數(shù)列表)
Java 中方法必須聲明在類的內(nèi)部,且被分為成員方法和靜態(tài)方法较店。
成員方法表示類的對象的一種行為士八,聲明時沒有關(guān)鍵字 static
。
public int add(int x, int y) {
return x + y;
}
靜態(tài)方法使用關(guān)鍵字 static
聲明梁呈,屬于類的行為婚度,或稱作類對象的行為,因此調(diào)用時無需創(chuàng)建任何對象官卡。main()
方法就是最常見的靜態(tài)方法蝗茁。
public static void main(String[] args) {
}
Varargs
Varargs 即參數(shù)長度不確定,簡稱變長參數(shù)寻咒。Java 使用符號 ...
表示變參哮翘,但是變參只能出現(xiàn)在參數(shù)列表的最后一個,即 sum(int x, int y, int...n)
是合法的毛秘,但 sum(int x, int...n, int y)
或 sum(int...n, int x, int y)
都是非法的饭寺。
聲明一個變參方法
例:
class Calculator {
public void sum(int... n) {
int result = 0;
for (int i : n) {
result += i;
}
System.out.println(result);
}
}
調(diào)用該方法
Calculator calculator = new Calculator();
calculator.sum(1, 2, 3);
參數(shù)默認值
Java 不支持參數(shù)默認值,所以調(diào)用時必須為每一個參數(shù)賦值
例:
private static void say(String name, String word) {
if (word == null) {
System.out.println(word + " " + name);
}
}
say("Peter", null);
返回值
Java 中方法除非返回值類型聲明為 void
表示沒有返回值,否則必須在方法中調(diào)用 return
語句返回到調(diào)用處佩研。
例:
public int add(int x, int y) {
return x + y;
}
Lambda 表達式
序章已經(jīng)說過了,Lambda 只是匿名方法的語法糖
例:
Java 8 以前實現(xiàn)匿名內(nèi)部類的方式
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
System.out.println("Perform Click");
}
});
Java 1.8 使用 Lambda 表達式簡化原來的調(diào)用方式
button.addActionListener(e -> System.out.println("Perform Click"));
函數(shù)接口
定義一個函數(shù)接口
@FunctionalInterface
interface Excite {
String accept(String from);
}
以上使用了注解 @FunctionalInterface
霞揉,在 Java 1.8 的初期版本這個注解用于標示一個接口為函數(shù)接口旬薯,但在現(xiàn)在的版本這個注解已經(jīng)僅僅是個標識符了,可以進行省略适秩。
使用 Lambda 表達式
Lambda 表達式的基本語法為
(參數(shù)列表) -> {執(zhí)行語句}
如果執(zhí)行語句只有一句的話可以省略包裹其的花括號
例:
Excite excite = (word) -> word + "!!";
然后我們可以很方便的調(diào)用這個接口
excite.accept("Java")
如果 Lambda 語句只有一個語句且只有一個參數(shù)绊序,且該語句調(diào)用的是一個靜態(tài)方法,則可以使用符號 ::
進一步縮減代碼
Excite hello = (w) -> String.valueOf(w);
以上等同于
Excite hello = String::valueOf;
如果 Lambda 語句只有一個語句秽荞,且該語句為使用類的無參構(gòu)造方法創(chuàng)建類的實例骤公,則也可以使用符號 ::
進一步縮減代碼
Excite hello = (w) -> new Word();
以上等同于
Excite hello = Word::new;
多個參數(shù)
函數(shù)接口也可以接收多個參數(shù),這些參數(shù)可以為泛型而不是具體類型扬跋,實際上使用泛型的函數(shù)接口更為常見
以下定義了一個接收兩個參數(shù) F1
和 F2
阶捆,返回 T
類型的接口
interface Convert<F1, F2, T> {
T convert(F1 from1, F2 from2);
}
使用該接口
Convert<Integer, Integer, String> convert = (x, y) -> {
int result = x + y;
return x + " plus " + y + " is " + result;
};
System.out.println(convert.convert(1, 2)); // 1 plus 2 is 3
變參
在 Lambda 表達式中也一樣可以使用變參
例:
定義一個含有變參的接口
interface Contact<F, T> {
T accept(F... from);
}
使用該接口
Contact<String, String> contact = (args) -> String.join(",", args);
contact.accept("Java", "Groovy", "Scala", "Kotlin");
內(nèi)置函數(shù)接口
通過以上例子我們可以看到要想使用 Lambda 表達式我們必須先定義一個函數(shù)接口,這樣用法太過麻煩钦听。所以 Java 提供了一些內(nèi)置的函數(shù)接口供我們調(diào)用.
Predicate
Predicate 接口用于接收一個參數(shù)并返回 Boolean 值洒试,主要用于處理邏輯動詞。該接口還有一個默認方法 negate()
用于進行邏輯取反朴上。
Predicate<String> predicate = (s) -> s.length() > 0;
assert predicate.test("foo");
assert !predicate.negate().test("foo");
Function
Function 接口接收一個參數(shù)并返回單一結(jié)果垒棋,主要用于進行類型轉(zhuǎn)換等功能。該接口也提供了一個 andThen()
方法用于執(zhí)行鏈式操作痪宰。
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
assert toInteger.apply("123") == 123;
assert backToString.apply("123").equals("123");
Supplier
Supplier 接口沒有參數(shù)叼架,但是會返回單一結(jié)果,可以用于實現(xiàn)工廠方法衣撬。
Supplier<Calculator> calculatorSupplier = Calculator::new;
assert calculatorSupplier.get().add(1, 2) == 3;
Consumer
Consumer 接口接收一個參數(shù)乖订,沒有返回值,用于對傳入的參數(shù)進行某些處理具练。該接口也提供了 andThen()
方法垢粮。
Consumer<Person> calculatorConsumer = (p) ->
System.out.println("The name is " + p.getName());
calculatorConsumer.accept(new Person("Peter"));
Comparator
Comparator 接口接收兩個參數(shù),返回 int 值靠粪,用于進行排序操作蜡吧。該接口提供了 reversed()
方法進行反序排列。
Comparator<Person> comparator = (p1, p2) ->
p1.getAge().compareTo(p2.getAge());
Person john = new Person("John", 20);
Person alice = new Person("Alice", 18);
assert comparator.compare(john, alice) > 0;
assert comparator.reversed().compare(john, alice) < 0;
函數(shù)接口作為參數(shù)
函數(shù)接口也可以作為參數(shù)來使用
private static int max(int[] arr, Function<int[], Integer> integerFunction) {
return integerFunction.apply(arr);
}
使用該接口
int maxValue = max(new int[]{3, 10, 2, 40}, (s) -> {
int max = -1;
for (int n : s) {
if (max < n) max = n;
}
return max;
});
assert maxValue == 40;
閉包
Java 中的閉包
Java 中和閉包相近的概念就是匿名類以及本章所說的 Lambda 表達式占键。但是這兩種都不是真正意義上的閉包昔善。
先看一個例子:
interface Excite {
String accept(String from);
}
private static Excite excite(String s) {
Excite e = from -> {
from = "from=" + from;
return from + "," + s;
};
return e;
}
Excite excite = excite("hello");
System.out.println(excite.accept("world")); // from=world,hello
以上例子中 e
為 Lambda 表達式,其定義在 excite()
方法中并且訪問了該方法的參數(shù)列表畔乙。按照生命周期君仆,excite()
的參數(shù) s
應該在方法被調(diào)用后就自動釋放,即 Excite excite = excite("hello")
調(diào)用后就不存在參數(shù) hello
了,但實際打印語句還是打印出來了返咱。
這一表現(xiàn)形式非常像閉包钥庇,因為參數(shù)明顯在其生命周期外還存活。但是如果我們在 Lambda 表達式內(nèi)試圖修改參數(shù) s
的值后編譯器會報 s
必須為 final
咖摹,這就是說該變量實際并不是自由變量评姨,所以并不是真正的閉包。
如果查看 Groovy 的閉包形式你可以發(fā)現(xiàn) Groovy 實際也是通過實現(xiàn)繼承自 Closure
類的匿名內(nèi)部類來實現(xiàn)閉包形式的萤晴,這一點與 Java 一致吐句。所以理論上 Java 也能實現(xiàn)真正的閉包,至于 1.8 為什么沒有這么做就不得而知了店读。
Groovy 篇
方法
定義方法
完整的 Groovy 方法定義語法為
[訪問控制符] [static] def 方法名(參數(shù)列表)
Groovy 也和 Java 一樣有成員方法和靜態(tài)方法之分嗦枢。
成員方法表示類的對象的一種行為,聲明時沒有關(guān)鍵字 static
屯断。
def add(x, y) {
x + y
}
靜態(tài)方法使用關(guān)鍵字 static
聲明文虏,屬于類的行為,或稱作類對象的行為殖演,因此調(diào)用時無需創(chuàng)建任何對象择葡。main()
方法就是最常見的靜態(tài)方法。
static def main(String[] args) {
}
Varargs
Groovy 表示變參的方式與 Java 一樣剃氧,且變參也只能出現(xiàn)在參數(shù)列表的最后一個敏储。
聲明一個變參方法
class Calculator {
def sum(int ... n) {
print(n.sum())
}
}
調(diào)用該方法
def calculator = new Calculator()
calculator.sum(1, 2, 3)
參數(shù)默認值
Groovy 支持參數(shù)默認值,但是一旦使用參數(shù)默認值時朋鞍,參數(shù)列表的最后一個或最后幾個參數(shù)都必須有默認值已添,即 def foo(x, y, z ="bar")
和 def foo(x, y = "bar", z = "bar")
都是合法的,但是 def foo(x, y = "bar", z)
則是非法的滥酥。
例:
static def say(name, word = "Hello") {
println("$word $name")
}
say("Peter")
返回值
Groovy 中由動態(tài)類型的存在更舞,所以可以不聲明返回值類型。并且在 Groovy 中方法的最后一個語句的執(zhí)行結(jié)果總是回被返回(也適用于無返回值的時候)坎吻,所以也無需 return
語句缆蝉。
例:
def add(x, y) {
x + y
}
Lambda 表達式
Groovy 目前還不支持 Java 1.8 的特性,所以 Java 中的 Lambda 表達式和對應的函數(shù)式接口無法在 Groovy 中直接使用瘦真。但是 Groovy 本身支持閉包刊头,且閉包就是以 Lambda 表達式的形式存在的,所以閉包和 Lambda 合在一節(jié)講诸尽。
閉包
概念
閉包是一種帶有自由變量的代碼塊原杂,其最顯著的特性就是能夠擴大局部變量的生命周期。與 Java 不同您机,Groovy 支持真正的閉包穿肄。
創(chuàng)建一個閉包
由于閉包是個代碼塊年局,所以一般意義上最簡單的閉包形式如下
{ println("foo") }
不過由于 Java 的普通代碼塊也是這樣的形式,所以為了避免混淆咸产,以上閉包必須寫成如下形式
{ -> println("foo") }
綜上所述矢否,閉包的語法為
{ 參數(shù)列表 -> 執(zhí)行語句 }
例:
{ x, y ->
println "$x plus $y is ${x + y}"
}
Groovy 中定義閉包實際是定義了一個繼承自 Closure
類的匿名內(nèi)部類,執(zhí)行閉包實際是執(zhí)行該類的實例的方法脑溢。這一點與 Java 非常相似僵朗。
字面量
閉包可以存儲在一個變量中,這一點是實現(xiàn)函數(shù)是一等公民的重要手段焚志。
例:
def excite = { word ->
"$word!!"
}
調(diào)用閉包
excite("Java")
或
excite.call("Groovy")
多參數(shù)
閉包的參數(shù)可以和方法的參數(shù)一樣擁有多個參數(shù)及默認值
例:
def plus = { int x, int y = 1 ->
println "$x plus $y is ${x + y}"
}
it
it
是個隱式參數(shù)衣迷,當閉包只有一個參數(shù)時畏鼓,使用 it
可以直接指代該參數(shù)而不用預先聲明參數(shù)列表酱酬。
例:
def greeting = { "Hello, $it!" }
println(greeting("Peter"))
Varargs
閉包也支持變參
例:
def contact = { String... args -> args.join(',') }
println(contact("Java", "Groovy", "Scala", "Kotlin"))
閉包作為參數(shù)
由于閉包本質(zhì)是 Closure
的子類,所以可以使用 Closure
作為參數(shù)的類型接收一個閉包
static def max(numbers, Closure<Integer> closure) {}
進一步簡化后
static def max(numbers, cls) {
cls(numbers)
}
傳入閉包
def maxValue = max([3, 10, 2, 1, 40]) {
def list = it as List<Integer>
list.max()
}
assert maxValue == 40
自執(zhí)行閉包
自執(zhí)行閉包即定義閉包的同時直接執(zhí)行閉包云矫,一般用于初始化上下文環(huán)境膳沽,Javascript 中常使用這種方法來初始化文檔。
定義一個自執(zhí)行的閉包
{ int x, int y ->
println "$x plus $y is ${x + y}"
}(1, 3) // 1 plus 3 is 4
Scala 篇
方法
定義方法
完整的 Scala 方法定義語法為
[訪問控制符] def 方法名(參數(shù)列表) [:返回值類型] [=] {}
Scala 可以省略變量定義的類型聲明和返回值類型让禀,但是在定義參數(shù)列表時則必須明確指定類型挑社。
例:
def add(x: Int, y: Int): Int = {
x + y
}
Scala 只有成員方法,沒有靜態(tài)方法巡揍,但是可以通過單例來實現(xiàn)靜態(tài)方法的功能痛阻,具體內(nèi)容見 Object 章節(jié)。
參數(shù)列表
Scala 中參數(shù)列表必須明確指定參數(shù)類型腮敌。如果一個方法沒有參數(shù)列表時阱当,可以省略小括號,但是調(diào)用時也不能加上小括號糜工。
例:
// 沒有小括號
def info(): Unit = {
println("This is a class called Calculator.")
}
println(info())
// 有小括號
def info2: Unit = {
println("This is a class called Calculator.")
}
println(info)
Varargs
Scala 使用 參數(shù)類型*
表示變參弊添。
聲明一個變參方法
class Calculator {
def sum(n: Int*) {
println(n.sum)
}
}
調(diào)用該方法
val calculator = new Calculator
calculator.sum(1, 2, 3)
_*
如果希望將一個 Sequence 作為參數(shù)傳入上一節(jié)的 sum()
方法的話編輯器會報參數(shù)不匹配。此時可以使用 _*
操作符捌木,_*
可以將一個 Sequence 展開為多個參數(shù)進行傳遞油坝。
例:
calculator.sum(1 to 3: _*)
參數(shù)默認值
Scala 同 Groovy 一樣支持參數(shù)默認值,但是一旦使用參數(shù)默認值時刨裆,參數(shù)列表的最后一個或最后幾個參數(shù)都必須有默認值澈圈。
def say(name: String, word: String = "Hello"): Unit = {
println(s"$word $name")
}
say("Peter")
返回值
Scala 中總是會返回方法內(nèi)部的最后一個語句的執(zhí)行結(jié)果,所以無需 return
語句帆啃。如果沒有返回值的話需要聲明返回值類型為 Unit
极舔,并此時可以省略 :Unit=
。如果方法沒有遞歸的話返回值類型也可以省略链瓦,但是必須使用 =
拆魏。
默認返回最后一行的執(zhí)行結(jié)果
def add(x: Int, y: Int): Int = {
x + y
}
無返回值的情況
def echo(): Unit = {}
無返回值時可以簡寫為以下形式
def echo() = {}
方法嵌套
Scala 支持方法嵌套盯桦,即一個方法可以定義在另一個方法中,且內(nèi)層方法可以訪問外層方法的成員渤刃。
例:
def testMethod(): Unit = {
var x = 1
def add(y: Int): Int = {
x + y
}
println(add(100))
}
Lambda 表達式
同 Groovy 一樣拥峦,閉包和 Lambda 也合在一節(jié)講。
閉包
同 Groovy 一樣卖子,Scala 也支持閉包略号,但是寫法有些不同。
創(chuàng)建一個閉包
由于閉包是個代碼塊洋闽,所以最簡單的閉包形式如下
例:
() => println("foo")
字面量
閉包可以存儲在一個變量中玄柠,這一點是實現(xiàn)函數(shù)是一等公民的重要手段。
例:
val excite = (word: String) =>
s"$word!!"
調(diào)用閉包
excite("Java")
或
excite.apply("Scala")
多參數(shù)
閉包的參數(shù)可以和方法的參數(shù)一樣擁有多個參數(shù)诫舅,但是同 Groovy 不一樣羽利,Scala 中閉包的參數(shù)不能有默認值,且參數(shù)列表為多個時必須將參數(shù)包裹在小括號內(nèi)刊懈。
例:
val plus = (x: Int, y: Int) =>
println(s"$x plus $y is ${x + y}")
_
_
是個占位符这弧,當閉包只有一個參數(shù)時,使用 _
可以直接指代該參數(shù)而不用預先聲明參數(shù)列表虚汛。
例:
val greeting = "Hello, " + _
println(greeting("Peter"))
Varargs
Scala 中閉包不支持變參
閉包作為參數(shù)
def max(numbers: Array[Int], s: (Array[Int]) => Int): Unit = {
s.apply(numbers)
}
傳入閉包
val maxValue = max(Array(3, 10, 2, 1, 40), (numbers) => {
numbers.max
})
也可以使用如下方式進行簡化
def max2(numbers: Array[Int])(s: (Array[Int]) => Int): Unit = {
s.apply(numbers)
}
maxValue = max2(Array(3, 10, 2, 1, 40)) { numbers =>
numbers.max
}
自執(zhí)行閉包
自執(zhí)行閉包即定義閉包的同時直接執(zhí)行閉包匾浪,一般用于初始化上下文環(huán)境,Javascript 中常使用這種方法來初始化文檔卷哩。
定義一個自執(zhí)行的閉包
例:
((x: Int, y: Int) => {
println(s"$x plus $y is ${x + y}")
})(1, 3) // 1 plus 3 is 4
Kotlin 篇
方法
定義方法
完整的 Kotlin 方法定義語法為
[訪問控制符] fun 方法名(參數(shù)列表) [:返回值類型] {}
Kotlin 可以省略變量定義的類型聲明蛋辈,但是在定義參數(shù)列表和定義返回值類型時則必須明確指定類型。
例:
fun add(x: Int, y: Int): Int {
return x + y
}
Kotlin 只有成員方法将谊,沒有靜態(tài)方法冷溶,但是可以通過單例來實現(xiàn)靜態(tài)方法的功能,具體內(nèi)容見 Object 章節(jié)瓢娜。
Varargs
Kotlin 使用 vararg
修飾參數(shù)來表示變參挂洛。
聲明一個變參方法
class Calculator {
fun sum(vararg n: Int) {
println(n.sum())
}
}
調(diào)用該方法
val calculator = Calculator()
calculator.sum(1, 2, 3)
參數(shù)默認值
Kotlin 同 Scala 一樣支持參數(shù)默認值,但是一旦使用參數(shù)默認值時眠砾,參數(shù)列表的最后一個或最后幾個參數(shù)都必須有默認值虏劲。
fun say(name: String, word: String = "Hello") {
println("$word $name")
}
say("Peter")
返回值
Kotlin 同 Java 一樣不會必須使用 return
語句來返回執(zhí)行結(jié)果。
例:
fun add(x: Int, y: Int): Int {
return x + y
}
方法嵌套
Kotlin 支持方法嵌套褒颈,即一個方法可以定義在另一個方法中柒巫,且內(nèi)層方法可以訪問外層方法的成員。
例:
fun testMethod() {
var x = 1
fun add(y: Int): Int {
return x + y
}
println(add(100))
}
Lambda 表達式
同 Scala 一樣谷丸,閉包和 Lambda 也合在一節(jié)講堡掏。
閉包
同 Scala 一樣,Kotlin 也支持閉包刨疼,但是寫法有些不同泉唁。
創(chuàng)建一個閉包
由于閉包是個代碼塊鹅龄,所以最簡單的閉包形式如下
例:
{ -> println("foo") }
字面量
閉包可以存儲在一個變量中,這一點是實現(xiàn)函數(shù)是一等公民的重要手段亭畜。
例:
val excite = { word: String ->
"$word!!"
}
調(diào)用閉包
excite("Java")
或
excite.invoke("Kotlin")
多參數(shù)
同 Scala 一樣扮休,Kotlin 中閉包的參數(shù)不能有默認值。
例:
val plus = { x: Int, y: Int ->
println("$x plus $y is ${x + y}")
}
it
同 Groovy 一樣閉包只有一個參數(shù)時可以使用 it
直接指代該參數(shù)而不用預先聲明參數(shù)列表拴鸵。但是不像 Groovy 那么方便析既,Kotlin 中這一特性僅能用作傳遞作為參數(shù)的閉包中而不能用在定義閉包時正驻。
以下閉包作為參數(shù)傳遞給方法 filter
val ints = arrayOf(1, 2, 3)
ints.filter {
it > 3
}
以下定義閉包時指定 it
是非法的
val greeting = { -> println(it) }
Varargs
Kotlin 中閉包不支持變參
閉包作為參數(shù)
fun max(numbers: Array<Int>, s: (Array<Int>) -> Int): Int {
return s.invoke(numbers)
}
傳入閉包
val maxValue = max(arrayOf(3, 10, 2, 1, 40)) {
it.max()!!
}
自執(zhí)行閉包
自執(zhí)行閉包即定義閉包的同時直接執(zhí)行閉包豫柬,一般用于初始化上下文環(huán)境陨闹,Javascript 中常使用這種方法來初始化文檔。
定義一個自執(zhí)行的閉包
{ x: Int, y: Int ->
println("$x plus $y is ${x + y}")
}(1, 3) // 1 plus 3 is 4
Summary
- ?除 Java 外聘芜,其它語言都支持參數(shù)默認值
文章源碼見 https://github.com/SidneyXu/JGSK 倉庫的 _16_method
小節(jié)