
Java Tutorial 第二堂(3)方法、類別與套件
Java Tutorial 第二堂(2)容器與流程語法 << 前情 雖然在 Java Tutorial 第二堂(1)中談過,Java 中並非每個值都是物件,不過 Java 基本上是個以物件導向為主要典範的語言,任何程式都以定義類別為出發點,即使只是個「哈囉!世界!」也不例外: public class HelloWorld { public static void main(String[] args) { System.out.println("哈囉!世界!"); } } 不過這看來不太像物件導向,若只是這樣的需求,似乎也不需要動用到物件導向,如果 Java 具有其他語言中函式(Function)的概念,不是可以更簡單一些,呼叫個 基本上,無論採用何種典範,關鍵在於架構程式時應思考的幾個重點,像是…
只有在腦海中清楚地思考過這幾個重點,才能在程式語言中採用適當的機制來加以實現,或者是在程式語言不支援時,想辦法自行實作類似機制,像是在 JavaScript 中,即使沒有名稱空間及套件機制,仍有開發者依各自需求實現各種風格的機制,來解決對應的問題。 幸運地,Java 中對於這幾個思考重點,在實作時提供的機制算是為完整,提供了像是靜態方法、類別(Class)與套件等支援。 靜態方法不管你願不願意,想撰寫第一個可執行的 Java 程式,就一定得接觸靜態方法,因為程式進入點就規定一定得是 ... Integer max1 = a > b ? a : b; ... Integer max2 = x > y ? x : y; ... 可以使用靜態方法來封裝程式片段,將流程中引用不同數值或變數的部份設計為參數,例如: /* 存為 Math.java */ class Math { static Integer max(Integer a, Integer b) { return a > b ? a : b; } } 靜態方法得定義在類別之中,如此一來,就可以在其他地方透過類別名稱來呼叫靜態方法,例如 ... Integer max1 = Math.max(a, b); ... Integer max2 = Math.max(x, y); ... 就某些程度上,包括靜態方法的類別充當了名稱空間,就像是 Python 中模組之作用,而類別中的靜態方法,就像是 Python 中的函式,而函式是一種抽象,對流程的抽象,因此如上定義了 Java 中有一些 API,就是以這樣的概念來實現,像是 那麼 Java 中的靜態方法,就只是函式的概念嗎?不!不只是這樣的,這篇文章稍後,馬上就可以看到靜態方法的其他應用… 類別如果只是將類別拿來當作靜態方法的名稱空間,並不是什麼物件導向,那類別的應用場合呢?…嗯…在你想要表達一組相關聯的數據時,例如,若你想表達帳戶資料,而帳戶有名稱、帳號與餘額,為了易於操作,可定義類別將它們視為一個整體: /* 存為 Account.java */ class Account { String name; String number; Integer balance; } 這麼一來,就使用 /* 存為 Bank.java */ class Bank { static Account account(String name, String number, Integer balance) { Account acct = new Account(); acct.name = name; acct.number = number; acct.balance = balance; return acct; } static void deposit(Account acct, Integer amount) { if(amount <= 0) { throw new IllegalArgumentException("amount must be positive"); } acct.balance += amount; } static void withdraw(Account acct, Integer amount) { if(amount > acct.balance) { throw new RuntimeException("balance not enough"); } acct.balance -= amount; } static String toStr(Account acct) { return String.format("Account(%s, %s, %d)", acct.name, acct.number, acct.balance); } } 當中是有關於帳戶建立、存款、提款等函式,你會這麼使用: public class Main { public static void main(String[] args) { Account acct = Bank.account("Java", "001", 100); Bank.deposit(acct, 500); Bank.withdraw(acct, 200); System.out.println(Bank.toStr(acct)); } } 實際上, class Account { private String name; private String number; private Integer balance; Account(String name, String number, Integer balance) { this.name = name; this.number = number; this.balance = balance; } void deposit(Integer amount) { if(amount <= 0) { throw new IllegalArgumentException("amount must be positive"); } this.balance += amount; } void withdraw(Integer amount) { if(amount > this.balance) { throw new RuntimeException("balance not enough"); } this.balance -= amount; } String toStr() { return String.format("Account(%s, %s, %d)", this.name, this.number, this.balance); } } 在 我們希望客戶端必須透過 如此定義之後,客戶端在使用上就容易得多了… public class Main { public static void main(String[] args) { Account acct = new Account("Java", "001", 100); acct.deposit(500); acct.withdraw(200); System.out.println(acct.toStr()); } } 是的!容易使用!在討論物件導向時,大家總是愛談可重用性(Reusability),然而要談到重用性的話,函式的重用性還高上許多,在考量物件導向時,易用性(Usability)其實才是它的重點。 套件假設現在你有一些 .java 與編譯完成的檔案,別人同樣也有一堆 .java 與 .class 檔案,你們的檔案現在得放在同一專案中,那麼檔案名稱衝突是有可能發生的,最好是為你們的 .java、.class 檔案分別開設目錄;另一方面,只使用外部類別充當名稱空間,也不是好的作法,當多個名稱空間階層時,就會有許多不便。 使用 Java 時,你可以在原始碼開頭使用 package tw.codedata.bank; public class Account { private String name; private String number; private Integer balance; public Account(String name, String number, Integer balance) { this.name = name; this.number = number; this.balance = balance; } public void deposit(Integer amount) { if(amount <= 0) { throw new IllegalArgumentException("amount must be positive"); } this.balance += amount; } public void withdraw(Integer amount) { if(amount > this.balance) { throw new RuntimeException("balance not enough"); } this.balance -= amount; } public String toString() { return String.format("Account(%s, %s, %d)", this.name, this.number, this.balance); } } 使用 注意,程式中宣告了 在類別中還定義了 假設你在先前的 Main.java 中宣告套件: package tw.codedata; import tw.codedata.bank.Account; public class Main { public static void main(String[] args) { Account acct = new Account("Java", "001", 100); acct.deposit(500); acct.withdraw(200); System.out.println(acct); } } 因為類別全名為 練習 7:運用類別與套件來組織程式 在練習用的檔案中,有個 exercises/exercise7/Bank 目錄,這個目錄符合 Gradle 架構,裏頭草草寫了一些類別與靜態方法,以及執行結果輸出的程式碼,請利用這邊介紹的類別與套件等語法,來重新組織當中可重用的程式碼,讓它們可以位於 最後,你完成的程式在實體架構上,應該會像是以下的圖片示意(如果不知道怎麼完成實作,記得參考練習用檔案中 solutions/exercise7/Bank 的成果 ): 再看靜態方法實際上,Java 的靜態方法並非只是將外部類別作為名稱空間,常見的運用之一是將靜態方法用來隱藏物件實作與建構細節,像是 Guava 的 類似的應用還有實現單例(Singleton),例如 Java 中的 public class Runtime { private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} // 略 ... } 建構式被設為 第二堂時間差不多到了,休息一下,接下來的第三堂課要來認識 Java 的 IDE、社群、文件以及更多的 API … |