從Functional Interface跟匿名內部類別(Anonymous Inner Classes)了解使用Lambda表示式的好處

  • 建立一個functional interface 名為StringAnalyzer

主要是提供一個虛擬方法供類別實作該方法,未來在類別裡就必須實作出內容以分析目標字串(target)裡是否含有我們想要搜尋的字串(searhStr),有就回傳true

1

  • 接下來,建立一個ContainsAnalyzer實作該介面

主要是分析目標字串(target)裡是否含有(contains) 我們想要搜尋的字串(searhStr)

2

  • 最後,開發AnalyzerTestWithoutHelper類別

利用For迴圈把strList這個陣列裡的每個字串取出來,然後透過StringAnalyzer的實例(實例參考為contains)幫我們分析出哪些來源字串(currentStr)裡有找到搜尋的字串(searhStr),並將來源字串print出來。

3

然而,為了充份實作「封裝」的精神,我們應該進一步把屬於商業邏輯的程式碼變成一個方法(那就是原本的for迴圈那一段),範例如下:

其中searchArr()方法需要輸入3個參考,包括來源字串陣列, 想要搜尋的字串,和StringAnalyzer物件實例,最後藉由StringAnalyzer的實例會幫我們找出來源字串陣列裡符合分析結果的字串。

3_4

接下來我們就把AnalyzerTestWithoutHelper類別改寫成AnalyzerTestWithHelper類別,其中封裝for迴圈的searchArr()方法就是名符其實的Helper方法,程式範例如下:

4

上述的範例,我們是先建立一個ContainsAnalyzer類別,然而這個類別也許執行程式時只會用到一次,為了這一次就特地建立一個類別似乎有點不適合,所以我們也許可以考慮使用匿名內部類別(Anonymous Inner Classes),就可以將上述步驟(2),(3)合併在一起,範例如下:

5

完整的程式範例如下:

6

<接下來就進入另外一個主題了>

仔細比較AnalyzerTestWithHelpler類別(沒有使用匿名內部類別)跟AnalyzerTestWithoutAnonymousClass類別(有使用匿名內部類別)程式碼字元數,你會發現兩者差不了多少,意思就是使用匿名內部類別也不會使程式碼減少多少,反而讓人覺得寫法很複雜。

Java 8開始,我們可以使用Lambda表示式改善上述的例子以便能真正簡化程式碼的撰寫,使用Lambda表示式後,程式碼將變成這樣:

7

甚至變數名稱愈簡單,程式碼就更精簡,如下所示:

8

為何原本的匿名內部類別的語法可以改成用Lambda表示式的方式呈現,關鍵在於「編譯器如何善用已知的資訊」

我們就一步一步來分析。

首先,在呼叫searchArr()方法時,其實編譯器已經知道第三個參數要傳入的是StringAnalyzer的參考變數,所以程式碼跟本不用強調new StringAnalyzer(){},假設我們就刪除之:

9

再來因為StringAnalyzer是functional interface,唯一的方法的定義相當清楚(這就是為什麼我們的標題要強調「從Functional Interface…」),所以方法的「名稱」、「回傳型別」、「存取層級」等都已經知道,故public boolean analyze{}就可以不用寫出來,假設我們就刪除之:

10

進一步修改語法把「return」 關鍵字改成「->」同時移除行尾的「;」因為已經不是一個陳述式(statement)的結果了,變成是表示式(expression)了

11

因為已知道functional interface上唯一的方法的定義,所以二個參數的型別analyzer(String ?, String ?),也可以不用強調,假設我們就刪除之:

12

甚至變數名稱愈簡單,程式碼就更精簡,把target改為t,search改為s,就變成如下所呈現的最精簡的程式碼了,大公告成!!!

13

String類別除了提供contains()方法外,還有startsWith(), endsWIth(),所以未來我們可以很簡單的改變程式碼的邏輯,範例如下:

15

最後, Lambda表示式也可以用在變數,如此就可以定義一次,使用多次了,範例如下:

16

 

Java package關鍵字的用法

以下是一個Java Class(類別)的結構:

 

package <package_name>;

import <other_packages>;

 

public class ClassName {

    <variables(also known as fields)>;

    <constructor(s)>;

    <other methods>; 

 }

ClassName{ }內定義的是某一個Class(類別)的程式內容。然而這不是這篇文章的重點,這篇文章的重點在package的用法。當類別一開始有陳述package關鍵字時,就代表給予這個類別一個名稱空間(Namespace)主要用意是防止跟其他類別混淆(就算是相同類別名字但屬於不同名稱空間,仍然能被區分出是不同類別)

另外就是相同package的類別表示那些類別的程式碼會存放在作業系統的同一個資料夾內

如果我們所撰寫的類別沒有定義名稱空間(Namespace),編譯出來的.class都放置在同一個資料夾下,這不是個很好的管理方式。

舉個例子:

在公司可能會有不同的開發部門,若不以資料夾方式來分別放置不同部門開發的.class檔。那麼若A部門寫了個Cust類別並編譯為Cust.class;B部門也寫了個Cust類別,也編譯為Cust.class。結果,因為這二個部門所開發的.class檔都放在同一個資料夾內,可想而知,相同名字的檔案是不是就會發生檔案覆蓋的問題。

所以Java提供了package關鍵字讓開發人員能設計自己的名稱空間(Namespace),主要也就是在防止這類的問題。

然而在操作上需要注意一些事項:

首先,在一個.java檔裡面,package語句一定要放在程式碼的最前面。

接下來我們來看一個例子:

在/home/oracle/myJava_progs資料夾裡,目前只有一個已開發好的Java 程式,檔名為PackageTest.java

[oracle@oracleDB ~]$ cd /home/oracle/myJava_progs

[oracle@oracleDB myJava_progs]$ ls

PackageTest.java

程式碼如下:

[oracle@oracleDB myJava_progs]$ cat PackageTest.java

package com.example.domain;

class BeRefed{

protected int x = 10;

}

public class PackageTest{

public static void main(){

BeRefed a = new BeRefed();

System.out.println(a.x);

}

}

看得出來在第一行有使用package關鍵字來定義該.java檔內的Classes(類別們)要放置、保存的「名稱空間(Namespace)」

然而當我們使用javac來編譯PackageTest.java時,所產生的兩個.class檔BeRefed.class及PackageTest.class所存放的資料夾卻是在/home/oracle/myJava_progs下,而非我們所預期的/home/oracle/myJava_progs/com/example/domain資料夾下

莫非是因為我們沒有手動在/home/oracle/myJava_progs路徑下建置./com/example/domain 資料夾?

現在我們就來證明看看:

首先,先將原本的BeRefed.class及PackageTest.class刪除

[oracle@oracleDB myJava_progs]$ rm *.class

[oracle@oracleDB myJava_progs]$ ls

PackageTest.java

接下來在/home/oracle/myJava_progs路徑下建置./com/example/domain資料夾

[oracle@oracleDB myJava_progs]$ mkdir -p com/example/domain/

[oracle@oracleDB myJava_progs]$ ls

com  PackageTest.java

[oracle@oracleDB myJava_progs]$ ls /home/oracle/myJava_progs/com/example/domain

[oracle@oracleDB myJava_progs]$ javac PackageTest.java

事實證明,編譯完的.class檔仍然沒有出現在/home/oracle/myJava_progs/com/example/domain資料夾下,還是在原本的/home/oracle/myJava_progs路徑下。

[oracle@oracleDB myJava_progs]$ ls /home/oracle/myJava_progs/com/example/domain

[oracle@oracleDB myJava_progs]$ ls

BeRefed.class  com  PackageTest.class  PackageTest.java

要讓編譯完成後.class檔存放在我們希望的目錄路徑下,執行javac時需要加上-d的選項,透過

-d 可以指定編譯完成後的.class 檔案存放的路徑。

接下來我們就來操作一遍給大家看:

假設我們希望未來編譯好的.class檔都統一存放講/home/oracle/myJava_class目錄路徑下,目前該目錄路徑下都沒任何子資料夾及檔案

[oracle@oracleDB myJava_progs]$ ls /home/oracle/myJava_class

[oracle@oracleDB myJava_progs]$

當我們執行javac時加上-d的選項指定編譯完成後的.class 檔案要存放在/home/oracle/myJava_class這個目錄路徑

[oracle@oracleDB myJava_progs]$ javac -d /home/oracle/myJava_class PackageTest.java

這時再來驗證一下,你將發現在/home/oracle/myJava_class目錄路徑下自動產生了./com/eample/domain 子資料夾,而編譯完成後的.class 檔案就存放在該子資料夾下

[oracle@oracleDB myJava_progs]$ ls /home/oracle/myJava_class

com

[oracle@oracleDB myJava_progs]$ ls /home/oracle/myJava_class/com/example/domain/

BeRefed.class  PackageTest.class

[oracle@oracleDB myJava_progs]$

以上談到的就是package關鍵字的用法。

 

Oracle SQL Row Limiting Clause

針對Oracle Database Server,如果我們要達到Top-N查詢的效果,在Oracle Database 11g 版本之前,我們只能透過Inline View搭配rownum的效果操作,例如:

這隻SQL Statement將會回傳公司薪水最高的前5名員工資訊

select * from
(select employee_id,salary from employees order by salary desc)
where rownum <=5;

然而這樣的SQL 程式碼只能用在Oracle Database Server,如果同樣的語法在MS SQL Server上將不能執行成功,因為這樣的語法並不符合ANSI SQL的標準,它只是Oracle Database Server專屬的語法。

除此之外,在Oracle Database 11g 版本之前的做法,針對要求得薪水由高到低排名第6到第10名的這種Top-N查詢的需求,甚至要使用到集合運算子(SET Operators),例如:

select * from (select employee_id,salary from employees order by salary desc)
where rownum <=10
MINUS
select * from (select employee_id,salary from employees order by salary desc)
where rownum <=5;

好消息是,從Oracle Database 12c R1版本開始,我們可以使用row_limiting_clause 來開發符合ANSI SQL標準的Top-N查詢語法,語法如下:

SELECT …FROM …

[ WHERE … ]

[ ORDER BY … ]

[OFFSET offset { ROW | ROWS }]

[FETCH { FIRST | NEXT } [{ row_count | percent PERCENT }] { ROW | ROWS }

{ ONLY | WITH TIES }]

所以我們現在將上述兩個範例以使用row_limiting_clause的方式改寫,如下:

–求得公司薪水最高的前5名員工資訊
SELECT employee_id,last_name,department_id,salary FROM employees
ORDER BY salary DESC
FETCH NEXT 10 ROW ONLY;

–求得薪水由高到低排名第6到第10名的員工資訊
SELECT employee_id,last_name,department_id,salary FROM employees
ORDER BY salary DESC
OFFSET 5 ROWS
FETCH FIRST 5 ROWS ONLY;

補充說明:

(1)使用ROW or ROWS的效果是一樣的

(2)使用FIRST or NEXT的效果是一樣的

(3)–假設第7跟第8名是相同值,此時應該要使用WITH TIES取代ONLY)
SELECT employee_id,last_name,department_id,salary FROM employees
ORDER BY salary DESC
FETCH FIRST 7 ROWS WITH TIES;