Linux基本屬性
內部類
內部類(inner class) 是定義在另一個類中的類。為什麼需要使用內部類呢? 其主要原因有以下三點:
•內部類方法可以訪問該類定義所在的作用域中的資料, 包括私有的資料。
•內部類可以對同一個包中的其他類隱藏起來。
•當想要定義一個回撥函式且不想編寫大量程式碼時,使用匿名(anonymous) 內部類比較 便捷。
使用內部類訪問物件狀態
語法比較複雜。鑑於此情況, 我們選擇一個簡單但不太實用的例子說明內部類 的使用方式。下面將進一步分析 TimerTest 示例, 並抽象出一個 TalkingClock 類。構造一個 語音時鐘時需要提供兩個引數:釋出通告的間隔和開關鈴聲的標誌。
public class TalkingClock { private int interval: private boolean beep; public TalkingClock(int interval, boolean beep) { . . . } public void start() {... } public class TimePrinter implements ActionListener // an inner class { } . . . }
需要注意, 這裡的 TimePrinter 類位於 TalkingClock類內部。這並不意味著每個 TalkingClock 都有一個 TimePrinter 例項域 , 如前所示,TimePrinter物件是由 TalkingClock類的方法構造。
下面是 TimePrinter 類的詳細內容。需要注意一點,actionPerformed方法在發出鈴聲之前 檢查了 beep標誌
public class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + new Date()); if (beep) Toolkit.getDefaultToolkit().beep(); } }
令人驚訝的事情發生了。
內部類既可以訪問自身的資料域,也 可以訪問建立它的外圍類物件的資料域。
程式清單 6-7給出了一個測試內部類的完整程式。下面我們再看一下訪問控制。如果有 一個 TimePrinter 類是一個常規類,它就需要通過 TalkingClock 類的公有方法訪問 beep標誌, 而使用內部類可以給予改進, 即不必提供僅用於訪問其他類的訪問器。
[注] TimePrinter 類宣告為私有的。這樣一來, 只有 TalkingClock 的方法才能夠構造 TimePrinter 物件。只有內部類可以是私有類,而常規類只可以具有包可見性,或公有可見性。//程式清單6-7 innerClass/InnerClassTest.java package innerClass; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.Timer; /** * This program demonstrates the use of inner classes. * @version 1.10 2004-02-27 * @author Cay Horstmann */ public class InnerClassTest { public static void main(String[] args) { TalkingClock clock = new TalkingClock(1000, true); clock.start(); // keep program running until user selects "Ok" JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } /** * A clock that prints the time in regular intervals. */ class TalkingClock { private int interval; private boolean beep; /** * Constructs a talking clock * @param interval the interval between messages (in milliseconds) * @param beep true if the clock should beep */ public TalkingClock(int interval, boolean beep) { this.interval = interval; this.beep = beep; } /** * Starts the clock. */ public void start() { ActionListener listener = new TimePrinter(); Timer t = new Timer(interval, listener); t.start(); } public class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } } }
內部類的特殊語法規則
事實上,使用外圍類引用的 正規語法還要複雜一些。表示式
OwterClass.this
表示外圍類引用。例如,可以像下面這樣編寫 TimePrinter 內部類的 actionPerformed方法:
public void actionPerformed(ActionEvent event) { . . . if (TalkingClock.this,beep) Toolkit.getDefaultToolkitO.beep(); }
反過來,可以採用下列語法格式更加明確地編寫內部物件的構造器:
outerObject.n&H InnerClass{construction parameters)
例如,
ActionListener listener = this.new TimePrinter();
在這裡,最新構造的 TimePrinter 物件的外圍類引用被設定為建立內部類物件的方法中的 this 引用。這是一種最常見的情況。通常,this 限定詞是多餘的。不過,可以通過顯式地命名 將外圍類引用設定為其他的物件。
需要注意, 在外圍類的作用域之外,可以這樣引用內部類:
OuterClass.InnerClass
[注] 內部類中宣告的所有靜態域都必須是 final。原因很簡單。我們希望一個靜態域只 有一個例項, 不過對於每個外部物件, 會分別有一個單獨的內部類例項。如果這個域不 是 final, 它可能就不是唯一的。 內部類不能有 static 方法。Java 語言規範對這個限制沒有做任何解釋。也可以允許有 靜態方法,但只能訪問外圍類的靜態域和方法。顯然,Java 設計者認為相對於這種複雜 性來說, 它帶來的好處有些得不償失。區域性內部類
如果仔細地閱讀一下 TalkingClock 示例的程式碼就會發現, TimePrinter 這個類名字只在 start 方法中建立這個型別的物件時使用了一次。
當遇到這類情況時, 可以在一個方法中定義區域性類。
public void start(){ class TiiePrinter inpleients ActionListener { public void actionPerforaed(ActionEvent event) { System.out.println("At the tone, the tine is " + new DateO);
if (beep)
Toolkit.getDefaultToolkit.beep(); } } ActionListener listener = new TimePrinter(); Timer t = new Timer(interva1, listener); t.start(); }
區域性類不能用 public 或 private訪問說明符進行宣告。它的作用域被限定在宣告這個區域性 類的塊中。
區域性類有一個優勢, 即對外部世界可以完全地隱藏起來。 即使 TalkingClock 類中的其他 程式碼也不能訪問它。除 start 方法之外, 沒有任何方法知道 TimePrinter 類的存在。
由外部方法訪問變數
與其他內部類相比較, 區域性類還有一個優點。它們不僅能夠訪問包含它們的外部類, 還 可以訪問區域性變數。不過,那些區域性變數必須事實上為 final。這說明,它們一旦賦值就絕不 會改變。
下面是一個典型的示例。這裡, 將 TalkingClock 構造器的引數 interva丨和 beep 移至 start 方法中。
public void start(int interval, boolean beep) { class TimePrinter implements ActionListener { public void actionPerformed(ActionEvent event) { Systea.out.println("At the tone, the tiie is " + new DateO); if (beep) Toolkit.getDefaultToolki10•beep(); } } ActionListener listener = new TimePrinter(); Timer t = new Timer(interval,listener); t.start(); }
請注意,TalkingClock 類不再需要儲存例項變數 beep 了,它只是引用 start 方法中的 beep 引數變數。
為了能夠清楚地看到內部的問題,讓我們仔細地考査一下控制流程。
1 ) 呼叫 start 方法。
2) 呼叫內部類 TimePrinter 的構造器, 以便初始化物件變數 listener。
3 ) 將 listener 引用傳遞給 Timer 構造器,定時器開始計時, start 方法結束。此時,start 方法的 beep 引數變數不復存在。
4 ) 然後,actionPerformed方法執行 if(beep)...。
匿名內部類
假如只建立這個類的一個物件,就不必命名了。這種類被稱為匿名內部類(anonymous inner class)。
public void start(int interval, boolean beep) { ActionListener listener = new ActionListenerO { public void actionPerformed(ActionEvent event) { System.out.println("At the tone, the time is " + new Date()); if (beep) Toolkit.getDefaultToolkit().beep(); } } ; Timer t = new Timer(interval, listener); t.start(); }
這種語法確實有些難以理解。它的含義是:建立一個實現 ActionListener 介面的類的新 物件,需要實現的方法 actionPerformed定義在括號內。
通常的語法格式為:
new SuperType(constructionparameters) { inner class methodsanddata }
其中, SuperType 可以是 ActionListener 這樣的介面, 於是內部類就要實現這個介面。 SuperType 也可以是一個類,於是內部類就要擴充套件它。
由於構造器的名字必須與類名相同, 而匿名類沒有類名,所以,匿名類不能有構造器。 取而代之的是,將構造器引數傳遞給超類(superclass) 構造器。尤其是在內部類實現介面的 時候, 不能有任何構造引數。不僅如此,還要像下面這樣提供一組括號:
new InterfaceType(){ methods and data }
如果構造引數的閉小括號後面跟一個開大括號, 正在定義的就是匿名內部類。
程式清單 6-8包含了用匿名內部類實現語音時鐘程式的全部原始碼。將這個程式與程式 清單 6-7相比較就會發現使用匿名內部類的解決方案比較簡短、更切實際、 更易於理解。
//程式清單 6-8 anonymousInnerClass/AnonymousInnerClassTest.java package anonymousInnerClass; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.Timer; /** * This program demonstrates anonymous inner classes. * @version 1.10 2004-02-27 * @author Cay Horstmann */ public class AnonymousInnerClassTest { public static void main(String[] args) { TalkingClock clock = new TalkingClock(); clock.start(1000, true); // keep program running until user selects "Ok" JOptionPane.showMessageDialog(null, "Quit program?"); System.exit(0); } } /** * A clock that prints the time in regular intervals. */ class TalkingClock { /** * Starts the clock. * @param interval the interval between messages (in milliseconds) * @param beep true if the clock should beep */ public void start(int interval, final boolean beep) { ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { Date now = new Date(); System.out.println("At the tone, the time is " + now); if (beep) Toolkit.getDefaultToolkit().beep(); } }; Timer t = new Timer(interval, listener); t.start(); } }
靜態內部類
有時候, 使用內部類只是為了把一個類隱藏在另外一個類的內部,並不需要內部類引用 外圍類物件。為此,可以將內部類宣告為 static, 以便取消產生的引用。
在內部類不需要訪問外圍類物件的時候, 應該使用靜態內部類。 有些程式設計師用嵌 套類 (nested class) 表示靜態內部類。
與常規內部類不同, 靜態內部類可以有靜態域和方法。
宣告在介面中的內部類自動成為 static 和 public 類。
程式清單 6-9 包含 ArrayAIg 類和巢狀的 Pair 類的全部原始碼。
//程式清單 6-9 staticInnerClass/StaticInnerClassTest.java package staticInnerClass; /** * This program demonstrates the use of static inner classes. * @version 1.01 2004-02-27 * @author Cay Horstmann */ public class StaticInnerClassTest { public static void main(String[] args) { double[] d = new double[20]; for (int i = 0; i < d.length; i++) d[i] = 100 * Math.random(); ArrayAlg.Pair p = ArrayAlg.minmax(d); System.out.println("min = " + p.getFirst()); System.out.println("max = " + p.getSecond()); } } class ArrayAlg { /** * A pair of floating-point numbers */ public static class Pair { private double first; private double second; /** * Constructs a pair from two floating-point numbers * @param f the first number * @param s the second number */ public Pair(double f, double s) { first = f; second = s; } /** * Returns the first number of the pair * @return the first number */ public double getFirst() { return first; } /** * Returns the second number of the pair * @return the second number */ public double getSecond() { return second; } } /** * Computes both the minimum and the maximum of an array * @param values an array of floating-point numbers * @return a pair whose first element is the minimum and whose second element * is the maximum */ public static Pair minmax(double[] values) { double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; for (double v : values) { if (min > v) min = v; if (max < v) max = v; } return new Pair(min, max); } }
第七章 異常、斷言和曰志
處理錯誤
也有可能是因為使用無效的陣列下標, 或者試圖使用 一個沒有被賦值的物件引用而造成的。使用者期望在出現錯誤時, 程式能夠採用一些理智的行 為。如果由於出現錯誤而使得某些操作沒有完成, 程式應該:
•返回到一種安全狀態,並能夠讓使用者執行一些其他的命令;
•允許使用者儲存所有操作的結果,並以妥善的方式終止程式。
為了能夠在程式中處理異常情況, 必須研究程式中可能會出現的錯誤和問題, 以及哪類 問題需要關注。
1.使用者輸入錯誤
除了那些不可避免的鍵盤輸人錯誤外, 有些使用者喜歡各行其是,不遵守程式的要求。例 如, 假設有一個使用者請求連線一個 URL,而語法卻不正確。在程式程式碼中應該對此進行檢 查, 如果沒有檢査,網路層就會給出警告。
2.裝置錯誤
硬體並不總是讓它做什麼,它就做什麼。印表機可能被關掉了。網頁可能臨時性地不能瀏 覽。在一個任務的處理過程中,硬體經常出現問題。例如,印表機在列印過程中可能沒有紙了。
3.物理限制
磁碟滿了,可用儲存空間已被用完。
4.程式碼錯誤
程式方法有可能無法正確執行。例如,方法可能返回了一個錯誤的答案,或者錯誤地調 用了其他的方法。計算的陣列索引不合法,試圖在散列表中查詢一個不存在的記錄, 或者試 圖讓一個空找執行彈出操作,這些都屬於程式碼錯誤。
異常分類
在 Java 程式設計語言中, 異常物件都是派生於 Throwable 類的一個例項。
需要注意的是,所有的異常都是由 Throwable 繼承而來,但在下一層立即分解為兩個分 支:Error 和 Exception。
Error類層次結構描述了 Java 執行時系統的內部錯誤和資源耗盡錯誤。應用程式不應該 丟擲這種型別的物件。 如果出現了這樣的內部錯誤, 除了通告給使用者,並盡力使程式安全地 終止之外, 再也無能為力了。這種情況很少出現。
在設計 Java 程式時, 需要關注 Exception 層次結構。這個層次結構又分解為兩個分支: 一個分支派生於 RuntimeException ; 另一個分支包含其他異常。劃分兩個分支的規則是: 由 程式錯誤導致的異常屬於 RuntimeException ; 而程式本身沒有問題, 但由於像 I/O 錯誤這類 問題導致的異常屬於其他異常。
派生於 RuntimeException 的異常包含下面幾種情況:
•錯誤的型別轉換。
•陣列訪問越界 。
•訪問 null 指標。
不是派生於 RuntimeException 的異常包括:
•試圖在檔案尾部後面讀取資料。
•試圖開啟一個不存在的檔案。
•試圖根據給定的字串查詢 Class 物件, 而這個字串表示的類並不存在。
Java語 言 規 範 將 派 生 於 Error 類 或 RuntimeException類的所有異常稱為非受查 ( unchecked) 異常,所有其他的異常稱為受查(checked) 異常。這是兩個很有用的術語,在 後面還會用到。編譯器將核查是否為所有的受査異常提供了異常處理器。
宣告受查異常
方法應該在其首部宣告所有可能丟擲的異常。這樣可以從首部反映出這個方法可能丟擲 哪類受査異常。
在自己編寫方法時, 不必將所有可能丟擲的異常都進行宣告。至於什麼時候需要在方法 中用 throws 子句宣告異常, 什麼異常必須使用 throws 子句宣告, 需要記住在遇到下面 4 種 情況時應該丟擲異常:
1 ) 呼叫一個丟擲受査異常的方法, 例如, FilelnputStream 構造器。
2 ) 程式執行過程中發現錯誤,並且利用 throw語句丟擲一個受查異常(下一節將詳細地 介紹 throw 語句)。
3 ) 程式出現錯誤, 例如,a[-l]=0 會丟擲一個 ArraylndexOutOffloundsException 這樣的 非受查異常。
4 ) Java 虛擬機器和執行時庫出現的內部錯誤。
如何丟擲異常
假設在程式程式碼中發生了一些很糟糕的事情。一個名為 readData 的方法正在讀取一個首 部具有下列資訊的檔案:
Content-length: 1024
然而,讀到 733 個字元之後檔案就結束了。我們認為這是一種不正常的情況,希望丟擲一個 異常。
首先要決定應該丟擲什麼型別的異常。將上述異常歸結為 IOException 是一種很好的選 擇。仔細地閱讀 Java API 文件之後會發現:EOFException異常描述的是“ 在輸人過程中, 遇 到了一個未預期的 EOF 後的訊號”。這正是我們要丟擲的異常。下面是丟擲這個異常的語句:
throw new EOFExceptionQ;
或者
EOFException e = new EOFExceptionO;
throw e;
下面將這些程式碼放在一起:
String readData(Scanner in) throws EOFException{ . . . while (…) { if (Mn.hasNextQ) // EOF encountered { if (n < len) throw new EOFExceptionQ; } ... } return s; }
EOFException類還有一個含有一個字串型引數的構造器。這個構造器可以更加細緻的 描述異常出現的情況。
String gripe = "Content-length: " + len + ", Received: " + n; throw new EOFException(gripe);
在前面已經看到, 對於一個已經存在的異常類, 將其丟擲非常容易 D 在這種情況下:
1 ) 找到一個合適的異常類。
2 ) 建立這個類的一個物件。
3 ) 將物件丟擲。 一旦方法丟擲了異常, 這個方法就不可能返回到呼叫者。也就是說, 不必為返回的預設 值或錯誤程式碼擔憂。
建立異常類
定義一個派生於 IOException 的類。 習慣上, 定義的類應該包含兩個構造器, 一個是預設的構造器;另一個是帶有詳細描述資訊 的構造器(超類 Throwable 的 toString 方法將會打印出這些詳細資訊, 這在除錯中非常有用)。
class FileFormatException extends IOException { public FileFormatException() {} public FileFormatException(String gripe) { super(gripe); } }
現在,就可以丟擲自己定義的異常型別了。
String readData(BufferedReader in) throws FileFormatException { ... while (. ..) { if (ch == -1 ) // EOF encountered { if (n < len) throw new FileFornatExceptionQ; } ... } return s; }