1. 程式人生 > >Java基礎教程(12)--深入理解類

Java基礎教程(12)--深入理解類

一.方法的返回值

  當我們在程式中呼叫方法時,虛擬機器將會跳轉到對應的方法中去執行。當以下幾種情況發生時,虛擬機器將會回到呼叫方法的語句並繼續向下執行:

  • 執行完方法中所有的語句;
  • 遇到return語句;
  • 方法丟擲一個異常(有關異常的內容將會在後面的文章中討論)。

  這裡我們重點介紹return語句。return語句用來返回一個值,當虛擬機器遇到return語句時將會立刻結束當前方法並帶著返回值回到呼叫此方法的地方。在宣告方法時,返回值的型別要和return語句裡返回的值的型別一致。如果方法沒有需要返回的值,可以將返回值設定為void。在返回值型別為void的方法中也可以使用return語句,格式為:

return;

  它的作用僅僅是為了結束函式的執行。如果在返回值型別為void的方法中返回一個值,將會出現編譯錯誤。   返回值的型別既可以是基本資料型別,也可以是引用型別。當返回值型別是基本資料型別時,它返回的是基本資料型別的值;當返回值型別是引用型別時,它返回的是對這個物件的引用。   當一個方法的返回值型別是一個類時,返回的值必須是對這個類或它的子類的物件的引用。當返回值型別是一個介面時,返回的值必須是對實現了這個介面的類的物件的引用。

二.this關鍵字

  在方法或構造器中,this用來引用當前物件,可以通過this來訪問當前物件的所有成員。   使用this關鍵字的最常見的原因是因為域會被方法中的引數或變數覆蓋。此時,使用this關鍵字可以非常清晰的分辨它們而不是給變數或引數換一個名字。就像下面這樣:

public class Point {
    public int x = 0;
    public int y = 0;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

  this關鍵字還可以用來代表構造方法。下面是另一個Rectangle類的實現:

public class Rectangle {
    private int x, y;
    private int width, height;
        
    public Rectangle() {
        this(0, 0, 1, 1);
    }
    public Rectangle(int width, int height) {
        this(0, 0, width, height);
    }
    public Rectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
    ...
}

  這樣可以提高程式碼的複用性而不必為每一個構造器都編寫類似的程式碼。在一個構造器中呼叫另一個構造器時,必須將這行程式碼放在第一行。

三.控制對類成員的訪問許可權

  許可權修飾符決定了其他類是否可以使用某個類或訪問某個類的成員。有以下兩種級別的許可權控制。

1.類級別的訪問許可權

  類級別的訪問許可權通過加在class關鍵字之前的許可權修飾符決定,它控制的是其他類對該類的訪問許可權,只有public或包私有(就是沒有許可權修飾符)兩種。public許可權意味著其他所有的類都可以使用該類,而包私有許可權則意味著只有在同一個包(有關包的概念將會在下一篇教程中介紹)中的類才可以使用該類。你可能在在後面的有關內部類的文章中會看到class關鍵字前面可以使用private或protected,但這個類實際上已經是某個類的成員,它的訪問許可權屬於成員級別的訪問許可權。

2.成員級別的訪問許可權

  成員級別的訪問許可權通過加在類的成員之前的許可權修飾符來決定,它控制的是其他類對該成員的訪問許可權,分為public、protected、包私有和private。public意味著其他所有的類都可以訪問該成員,protected意味著只有該類所在包中的其他類或該類的子類才可以訪問該成員,包私有意味著只有該類所在包中的其他類可以訪問該成員,private意味著該成員只能在該類中訪問。下面的表格顯示了它們的關係:   例如,下面的幾個類關係如下:   下面的表格顯示了這幾個類對Alpha類中被不同許可權修飾符修飾的成員的訪問許可權:   一般來說應該儘可能的對域使用private,除非它是一個公共的常量,這種情況下應該使用public。

四.靜態成員

1.靜態域

  當建立一個類的許多物件時,每個物件都有屬於自己的非靜態域,這些變數儲存在不同的記憶體位置。有時,我們希望某個域被所有物件共享,或者說這個域屬於整個類而不是每個物件。使用static修飾符的域被稱為靜態域或類變數,該類的每一個例項共享這個變數,這個變數存在於記憶體中的一個固定的位置。該類的任何例項都可以更改靜態域的值,也可以在不建立類例項的情況下訪問靜態域。   例如,假設要建立多個Bicycle物件併為每個物件分配一個ID,第一個物件ID為1。此ID號對於每個物件都是唯一的,因此是一個例項變數。同時,您需要一個變數來表示Bicycle已建立的物件數,以便知道要分配給下一個物件的ID。這個域與任何單個物件無關,而與整個類有關,可以將這個域設定為靜態域。例如:

public class Bicycle {
        
    private int cadence;
    private int gear;
    private int speed;
    private int id;
    private static int numberOfBicycles = 0;
        
    public Bicycle(int startCadence, int startSpeed, int startGear){
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;
        id = ++numberOfBicycles;
    }
}

  每當建立一個Bicycle類的例項時,numberOfBicycles的值都會加1,然後將它的值作為ID分配給這個例項。

2.靜態方法

  靜態方法是指使用static修飾的方法。和靜態域一樣,它屬於類,而不是某個例項。可以通過類名去呼叫它而不用先建立類的例項。使用下面的語法呼叫靜態方法:

ClassName.methodName(args);

  儘管也可以通過建立例項去訪問類的靜態方法,但不鼓勵這麼做。通常使用靜態方法去訪問靜態域,例如,可以通過一個靜態方法去訪問Bicycle類的靜態域numberOfBicycles:

public static int getNumberOfBicycles() {
    return numberOfBicycles;
}

  靜態域、靜態方法、非靜態域和非靜態方法之間的訪問規則如下:

  • 非靜態方法可以訪問其他非靜態方法和非靜態域;
  • 非靜態方法可以訪問靜態方法和靜態域;
  • 靜態方法可以訪問其他靜態方法和靜態域;
  • 靜態方法不能訪問非靜態方法和非靜態域。

  總的來說就是非靜態方法可以訪問所有成員,而靜態方法只能訪問靜態成員。

3.常量

  static關鍵字常和final關鍵字一起用來定義常量。final修飾符意味著域的值不能改變。例如,下面的變數定義了一個常量PI,它的值是圓周率:

static final double PI = 3.141592653589793;

  被final修飾的變數一旦賦值以後就不能再修改。常量的命名規範是大寫每個字母並且使用下劃線(_)分隔每個單詞。   如果使用fianl修飾引用型別的變數,並不是說這個變數引用的物件不能發生任何改變,而是說這個變數不能再去引用別的物件。

五.域的初始化

  現在回憶一下我們在《Java基礎教程(5)--變數》一文中學習過的有關域的預設值的內容。在宣告域時,如果不對它進行顯式的初始化,編譯器將賦予它一個預設值。如果是基本資料型別的域,根據它的型別,它的預設值將會是:   如果是引用型別的域,它的預設值將會是null。   除了這種預設的初始化行為,是否還有其他方式可以在使用物件前對域進行初始化呢?答案是肯定的。下面我們將分別介紹非靜態域和靜態域初始化行為。

1.初始化非靜態域

(1)預設初始化

  正如上文所說,在宣告域時,如果不對它進行賦值,編譯器將賦予它一個預設值。例如:

public class Bag {
    public int capacity;
}

  在建立完Bag類的物件以後,capacity域的值將會是0。

(2)顯式初始化

  也可以在宣告域的同時給它提供一個初始值,就像下面這樣:

public class Bag {
    public int capacity = 10;
}

(3)在構造方法中進行初始化

  構造方法也可以對域進行初始化:

public class Bag {
    public int capacity;

    public Bag(int capacity) {
        this.capacity = capacity;
    }
}

(4)非靜態程式碼塊

  下面定義了一個非靜態程式碼塊對域進行初始化:

public class Bag {
    public int capacity;

    {
        capacity = 10;
    }
}

  與非靜態程式碼塊對應的是靜態程式碼塊,馬上會在下文中介紹。編譯器會將初始化塊拷貝到每個構造方法最前面的位置,因此,這種方法可以用來在多個構造方法中共享程式碼塊。一個類中可以定義多個程式碼塊,它們可以出現在類體中的任何位置,它們的執行順序與原始碼中的定義順序相同。

(5)呼叫方法進行初始化

  下面呼叫了一個方法對域進行初始化:

public class Bag {
    public int capacity;

    private int initializeCapacity() {
        return 10;
    }
}

  當然實際編寫程式碼過程中這個方法的邏輯肯定沒有這麼簡單,這裡只是為了舉個例子。如果子類想要重用這個初始化方法可以將方法的訪問許可權設定為protected,但是同時應該使用final修飾符來禁止子類重寫(有關重寫的內容會在後續文章中進行介紹)這個方法(PS:Java Tutorial裡說在例項初始化期間呼叫非final方法可能會導致問題,我暫時沒想到會出現什麼問題,如果有知道的大神還請點撥一二,下面是原文的截圖)。   在瞭解完這幾種初始化的方式以後,我們就可以在編寫程式碼時根據它們的性質靈活選擇。但是如果這幾種初始化方式同時出現的話,它們的順序又是怎麼樣呢?由於預設初始化和顯示初始化都是在宣告域的同時完成的,這兩種方式只能同時出現一種;又由於使用方法對域進行初始化的方式只是一種獲取值的形式,並不屬於初始化時機的討論範圍之內,它在顯式初始化、構造方法和程式碼塊中都可以使用,所以實際上可以將這五種方式總結為三種。它們的順序如下(大於號代表左邊的順序早於右邊的):

預設初始化、顯示初始化>非靜態程式碼塊>構造方法

2.初始化靜態域

  與非靜態域相比,由於靜態域的初始化與類是否例項化無關,所以不討論在構造方法中對靜態域賦值的方式(不代表不可以這麼做,但是不建議將靜態域與類的例項之間建立任何聯絡)。其餘四種形式都有與之對應的初始化行為,預設初始化與顯式初始化較為簡單,這裡不再贅述。   與非靜態域的非靜態程式碼塊對應的是靜態程式碼塊,一個類可以有任意數量的靜態程式碼塊,它們可以出現在類中的任何位置,編譯器保證按照它們在原始碼中出現的順序呼叫靜態程式碼塊。   也可以使用靜態方法對靜態域賦值,就像非靜態域那樣。   靜態域的初始化順序如下:

預設初始化、顯示初始化>靜態程式碼塊