前言
在 Java 中,函式 (function) 一定要包在類別中,所以才會出現靜態函式 (static function) 這種和物件無關但又存在於類別中的函式。
在 Groovy 中,這個限制放寛了,我們可以在 Groovy 中直接撰寫頂層函式,Groovy 會幫我們自動轉為對應的 Java 函式,不需要人為介入。
函式 (Function) 是什麼
函式是一種程序抽象化 (procedure abstraction) 的語法特性,其用途在包住一段程式碼,將其使用有意義的名稱包裝起來,之後我們要調用這段程式碼時,就可以直接透過函式名稱呼叫 (call) 該函式,不用重覆撰寫相同的程式碼。
函式的虛擬碼如下:
returnType fn (param_a, param_b, param_c) {
/* Implement code here. */
}
由此可知,函式有以下要件:
- 函式名稱
- 函式的參數 (parameters):零到多個
- 函式的回傳值 (returning value):零到一個
- 函式本體,即實作細節
數學上的函式比較純粹 (pure),都是在進行運算。但電腦程式的函式依其作用分為兩種:
- 運算 (computing)
- 狀態 (state) 改變 (註)
第一種函式比較接近數學上的函式,像 java.lang.Math 類別的 sqrt 函式計算某個數字的平方根。對於純運算的函式,只要傳入相同的參數,就會得到相同的結果。
第二種函式則會改變程式的狀態,像是在終端機上印出文字、改變物件的屬性等。對於狀態改變的函式,重覆傳入相同的參數,有可能會得到相異的結果。因為在每輪函式呼叫間,程式的狀態已經改變了。
撰寫函式的原則是同一個函式儘量不要同時具有兩種作用,將進行運算的函式和狀態改變的函式分開來實作。
(註) 在函數式程式設計中,將狀態改變稱為副作用 (side effect)
宣告和使用函式
以下簡例宣告一個函式,並呼叫之:
/* Declare a function. */
void hello ()
{
println "Hello World"
}
/* Call a function. */
hello()
若不想標註資料型態,可改用以下寫法:
/* Declare a function. */
def hello ()
{
println "Hello World"
}
/* Call a function. */
hello()
這個函式沒有參數也沒什回傳值,在實務上這類函式用途較少,本例僅用來展示如何建立函式。
帶有參數或回傳值的函式
以下簡例建立一個帶有參數和回傳值的函式:
/* Create a function with parameters
and a return value. */
int add (int a, int b)
{
return a + b
}
assert 8 == add(3, 5)
同樣地,若不想標註資料型態,可改用以下寫法:
/* Create a function with parameters
and a return value. */
def add (a, b)
{
return a + b
}
assert 8 == add(3, 5)
大部分函式都會有參數,讓我們調整函式的行為。此外,用來進行運算的函式也會有回傳值。
設置函式的預設值 (Default Value)
Groovy 的函式可以加入預設值,如下例:
/* Create a function with a default parameter. */
String greet (name = "World")
{
"Hello ${name}"
}
/* Call a function with its default argument. */
assert greet() == "Hello World"
/* Call a function with a custom argument. */
assert greet("Michelle") == "Hello Michelle"
預設值會在沒有其他值時自動代入。
遞迴函式 (Recursive Function)
遞迴函式在函式中可呼叫自己,其用意為將問題逐漸縮小。遞迴函式包括兩個要素:
- 終止條件
- 遞移步驟
在練習遞迴函式時,通常是沒有掌握好這兩個要素,造成函式無法收斂。
費波那西數是一個常見的遞迴程式範例:
/* Create a recursive function. */
int fib (int n)
{
if (n <= 1) {
return n
}
fib(n - 1) + fib(n - 2)
}
/* Call a recursive function. */
println fib(10)
要寫好遞迴程式,要有明確的終止條件和遞移步驟。由於遞迴程式在程式設計中時常見且重要的主題,最好還是花一些時間練習。