天天看點

《程式設計導論(Java)·3.1.2 方法》之 副作用

4. 副作用

在一些語言如Pascal中,子程式被分成兩種:函數和過程。雖然Java沒有強制性地要求将方法區分為指令和函數,然而這種差別對于良好地設計程式有很大的幫助[1]。

首先說明一個概念:副作用(side effect)。副作用一般是針對操作(表達式)而言的,一個操作/表達式有“副作用”是指在對該表達式求值時,會改變程式的一個或多個資料,以緻再次對該表達式求值時,可能會得出不同的結果。事實上,Java的4種表達式語句如指派、自增自減、方法調用、對象建立都可能帶來副作用。

這裡讨論方法的副作用。一個方法的執行,如果在傳回一個值之外還導緻某些外部“狀态”發生變化,則稱該方法産生了副作用。這裡所謂“狀态”發生變化,可以是執行個體域或靜态變量被修改、方法的實參被修改(Java 中不會出現這種情況。但是實參為引用時,其指向的對象可能被修改進而産生副作用)、将資料傳遞給顯示器、列印機或存入檔案中等等。

當然,方法内部的表達式也會出現副作用,如果它僅僅影響局部變量而不影響外部狀态,則方法沒有副作用。基于副作用概念,定義兩個術語:

²       有傳回值而且沒有副作用的方法稱為函數(function)。

²       沒有傳回值的方法必然有副作用,除非它的方法體是空的或者方法沒有意義。是以,沒有傳回值的方法、有傳回值但有副作用的方法稱為過程(procedure)或指令(command)。簡言之,有副作用的方法稱為過程。

如此嚴格地定義出函數的概念,是因為函數使得系統的狀态穩定,函數的行為容易預測。更進一步,如果函數是純粹的函數(pure function,純函數)——它的輸出值依靠和僅僅依靠其輸入、對于相同的輸入總是傳回相同的值,(由于純函數的純粹和無副作用)對純函數的調用就能夠被一個值取代(或者說,将方法視為一個值),這就是函數式程式設計語言中著名的引用透明(referentialtransparency)特性。

例程 3‑3純函數

package semantics.method;
public class SideEffectsDemo{
    private static int x = 0;    
    //純函數(pure function)
    public static int times(int i,int j){
        return  i * j ;
    }
    //非純函數
    public static int m(int i,int j){
        return  i * j +x;
    }
}
           

在函數式程式設計語言(functionalprogramming language)如Haskell[2]中,尤其強調避免副作用。當然了,完全不産生副作用的程式設計語言是沒有任何用處的,例如資料顯示和存入檔案等等副作用都是必要的,強調無副作用的Haskell 語言,使用一種技術将它們分離出來,用一種安全的方式單獨執行。

Java是指令式面向對象語言,但能夠借鑒函數式語言的優點,也期待Java中加入重要的函數式語言的特性。随着Java 8的釋出,引入的λ表達式(Lambda Expressions)表明,Java開始大力引入函數式語言的特性。(They enable you to treat functionality as a method argument, or code as data.)

練習3-5:何謂方法、函數、純函數? 解釋副作用的含義。
練習3-6:實作方法,求f(x)=x^3 + 3x+1。注:書中x^3表示x*x*x.

[1] 參考RichardMitchell,Jim Mackim著,孟岩譯,Design by Contract原則與實踐。

[2] http://www.haskell.org/learning.html

《程式設計導論(Java)·3.1.2 方法》之 副作用