1. 程式人生 > 實用技巧 >Linux基本屬性

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;
}