函數式編程與面向對象編程的區別: 函數式編程將程序代碼看做數學中的函數, 函數本身是另一個函數的函數或返回值, 即高階函數.
Lambda 表達式
示例: 通過匿名類實現計算兩個int值的功能
public class HelloWorld { public static Calculate calculate(char opt) { Calculate result; if(opt == '+') { // 匿名類實現Calculate接口 result = new Calculate() { // 實現加法運算 @Override public int calculateInt(int a, int b) { return a + b; } }; }else { result = new Calculate() { // 實現減法運算 @Override public int calculateInt(int a, int b) { return a -b; } }; } return result; } public static void main(String[] args) { int n1 = 10; int n2 = 5; Calculate f1 = HelloWorld.calculate('+'); Calculate f2 = HelloWorld.calculate('-'); System.out.println(f1.calculateInt(n1, n2)); System.out.println(f2.calculateInt(n1, n2)); } }
上例中通過匿名類實現 calculateInt 方法. 現在通過 Lambda 表達式將該方法的 if-else 部分修改為:
if(opt == '+') { // Lambda 表達式 result = (int a, int b) -> { return a+b; }; }else { // Lambda 表達式 result = (int a, int b) -> { return a - b; }; }
Lambda 表達式是一個匿名函數 (方法) 代碼塊, 可以作為表達式、方法參數和方法返回值. 其標准語法形式為:
(參數列表) -> { // Lambda 表達式 }
函數式接口
Lambda 表達式實現的接口不是普通的接口, 是函數式接口, 這種接口只能有一個方法. 為防止在函數式接口中聲明多個抽象方法, Java 8 提供了一個聲明函數式接口的注解 “@FunctionalInterface”.
Lambda 表達式是一個匿名方法的代碼塊, 它實現的是在函數接口中聲明的方法, 返回的是該接口的一個實例.
Lambda 表達式簡化形式
省略參數形式
Lambda 表達式可以根據上下文環境推斷出參數類型. 上例中的 if-else 可以修改為:
if(opt == '+') { result = (a, b) -> { return a+b; }; }else { result = (a, b) -> { return a - b; }; }
省略參數小括號
Lambda 表達式中參數只有一個時, 可以省略參數小括號.
將接口 Calculable 中的 calculateInt 方法修改為:
int calculateInt(int a);
上例中的 if-else 可以修改為:
if(opt == "square") { result = a -> { return a * a; }; }
省略 return 和大括號
Lambda 表達式體中只有一條語句時, 可以省略 return 和大括號.
繼續上例中的 if-else 可以修改為:
if(opt == "square") { result = a -> a * a; }
作為參數使用 Lambda 表達式
Lambda 表達式常見用途之一是作為參數傳遞給方法. 這需要聲明參數類型為函數式接口類型.
public class HelloWorld { public void display(Calculate c, int a) { System.out.println(c.squareInt(a)); } public static void main(String[] args) { int n = 12; HelloWorld h = new HelloWorld(); // 傳入 Lambda 表達式作為參數 h.display(x -> x * x, n); } } // 定義接口 interface Calculate { // 計算兩個int的值 int squareInt(int a); }
訪問變量
Lambda 表達式可以訪問所在外層作用域內定義的變量, 包括成員變量和局部變量.
訪問成員變量
public class HelloWorld { private int value = 10; private static int staticValue = 5; public static Calculate add() { Calculate result = (int a, int b) -> { // add是靜態方法, 不能訪問非靜態變量, 只能訪問靜態變量 staticValue++; int c = a + b + staticValue; return c; }; return result; } public Calculate sub() { Calculate result = (int a, int b) -> { staticValue++; this.value++; //如果不與局部變量沖突, 可以省略this int c = a - b - staticValue - this.value; return c; }; return result; } } // 定義接口 interface Calculate { int calculateInt(int a, int b); }
捕獲局部變量
Lambda 表達式訪問作用域外層的局部變量時, 會發生 “捕獲變量” 情況. Lambda 表達式捕獲變量時, 會將變量當成 final 的, 無論該變量是否被 final 修飾.
方法引用
Java 8 之后增加了雙冒號 “::” 運算符, 該運算符用於 “方法引用” , 注意不是調用方法. “方法引用” 雖然沒有直接使用 Lambda 表達式, 但也與 Lambda 表達式有關, 與函數式接口有關.
方法引用分為: 靜態方法的方法引用和實例方法的方法引用. 語法形式如下:
類型名:: 靜態方法 // 靜態方法的方法引用 類型名:: 實例方法 // 實例方法的方法引用
被引用方法的參數列表和返回值類型, 必須與函數式接口方法的參數列表和返回值類型一致.
public class LambdaDemo { // 聲明被引用的靜態方法 public static int add(int a, int b) { return a + b; } // 聲明被引用的實例方法 public int sub(int a, int b) { return a - b; } // 聲明使用函數式接口實例為參數的方法 public static void display(Calculable c, int n1, int n2) { System.out.println(c.calculateInt(n1, n2)); } public static void main(String[] args) { int n1 = 10; int n2 = 5; // 引用靜態方法 display(LambdaDemo::add, n1, n2); LambdaDemo ld = new LambdaDemo(); // 引用實例方法 display(ld::sub, n1, n2); } } interface Calculable { int calculateInt(int a, int b); }
方法引用就是使用其他類的方法代替了 Lambda 表達式, 使引用的方法起到 Lambda 表達式的作用.
異常處理
Java 中異常封裝成為類 Exception, 此外, 還有 Throwable 和 Error 類. 異常類繼承層次如圖:
異常基類 Throwable 有幾個常用方法:
String getMessage(): 獲得發生異常的詳細信息.
void printStackTrace(): 打印異常堆棧跟蹤信息.
String toString(): 獲得異常對象的描述.
Throwable 有兩個子類 Error 和 Exception.
Error
Error 是程序無法恢復的嚴重錯誤, 只能讓程序終止.
Exception
Exception 是程序可以恢復的異常. 該類可以分為: 受檢查異常和運行時異常.
受檢查異常
編譯器會檢查這類異常是否進行了處理, 即要么捕獲 (try-catch 語句), 要么拋出 (通過在方法后聲明 throws), 否則會發生變異錯誤.
運行時異常
編譯器不檢查這類異常是否進行了處理. 但由於沒有進行異常處理, 一旦運行時異常發生就會導致程序終止.
對運行時異常不采用拋出或捕獲處理方式, 而是應該提前預判, 防止發生這種異常.
捕獲異常
當前方法有能力解決時, 則捕獲異常進行處理; 沒有能力解決, 則拋給上層調用方法處理. 上層調用方法也無力解決時, 繼續拋給它的上層調用方法. 如果所有方法都沒有處理該異常, JVM 會終止程序運行.
try-catch 語句
語法格式:
try { // 可能發生異常的語句 }catch(Throwable e) { // 異常處理 }
try 代碼塊中包含可能發生異常的代碼語句. 每個 try 代碼塊可以伴隨一個或多個 catch 代碼塊, 用於處理 try 代碼塊中可能發生的異常.
多個異常類之間存在父子關系時, 捕獲異常順序與 catch 代碼塊的順序有關. 一般先捕獲子類, 后捕獲父類, 否則子類捕獲不到.
多重捕獲
Java 7 推出了多重捕獲 (multi-catch) 技術, 在 catch 中多重捕獲異常用 “|” 運算符連接.
try { ... }catch(IOException | ParseException e) { ... }
釋放資源
有時在 try-catch 語句中會占用一些非 Java 資源. 為了確保這些資源可以釋放, 可以使用 finally 代碼塊或 Java 7 之后提供自動資源管理技術.
finally 代碼塊
try-catch 語句后面還可以跟一個 finally 代碼塊:
try { ... }catch(Throwable e) { ... }fianlly { ... }
無論是否發生異常, finally 代碼塊都會執行.
自動資源管理
Java 7 之后提供了自動資源管理技術. 自動資源管理是在 try 語句上的擴展, 語法如下:
try(聲明或初始化資源語句) { ... }catch(Throwable e) { ... }
在 try 語句后面追加聲明或初始化資源語句, 可以有多條語句, 多條語句間使用分號 “;” 分隔.
throws 與聲明方法拋出異常
方法后面聲明拋出異常使用 “throws” 關鍵字. 多個異常間使用逗號 (,) 分隔.
自定義異常類
實現自定義異常類需要繼承 Exception 類或其子類. 如果自定義運行時異常類需要繼承 RuntimeException 類或其子類.
自定義異常類主要是提供兩個構造方法:
public class MyException extends Exception { public MyException{} public MyException(String message) { super(message); } }
throw 與顯式拋出異常
throws 用於方法后聲明拋出異常, throw 關鍵字用來人工引發異常.
throw new Exception("業務邏輯異常");
