Java™ 教程(類的更多方面)

類的更多方面

本節介紹依賴於使用對象引用的類的更多方面以及你在前面的對象部分中瞭解到的點運算符。java

從方法返回值

方法返回到調用它的代碼。程序員

  • 完成方法中的全部語句。
  • 到達return語句。
  • 或拋出異常(稍後介紹)。

以先發生者爲準。編程

你在方法聲明中聲明方法的返回類型,在方法體內,使用return語句返回值。segmentfault

聲明爲void的任何方法都不返回值,它不須要包含return語句,但它可能會這樣作,在這種狀況下,可使用return語句分支出控制流程塊並退出該方法,而且能夠像這樣使用:數組

return;

若是你嘗試從聲明爲void的方法返回值,則會出現編譯器錯誤。app

任何未聲明爲void的方法都必須包含帶有相應返回值的return語句,以下所示:編程語言

return returnValue;

返回值的數據類型必須與方法聲明的返回類型匹配,你不能從聲明爲返回布爾值的方法返回一個整數值。函數

在對象部分中討論的Rectangle類中的getArea()方法返回一個整數:this

// a method for computing the area of the rectangle
public int getArea() {
   return width * height;
}

此方法返回表達式width * height求值的整數。spa

getArea方法返回基本類型,方法還能夠返回引用類型,例如,在一個操做Bicycle對象的程序中,咱們可能有這樣的方法:

public Bicycle seeWhosFastest(Bicycle myBike, Bicycle yourBike,
                              Environment env) {
    Bicycle fastest;
    // code to calculate which bike is 
    // faster, given each bike's gear 
    // and cadence and given the 
    // environment (terrain and wind)
    return fastest;
}

返回類或接口

若是此部分讓你感到困惑,請跳過它並在完成接口和繼承課程後返回該部分。

當一個方法使用類名做爲其返回類型時,例如whosFastest,返回對象的類型類必須是返回類型的子類或確切的類。假設你有一個類層次結構,其中ImaginaryNumberjava.lang.Number的子類,而java.lang.Number又是Object的子類,以下圖所示。

classes-hierarchy.gif

如今假設你有一個聲明爲返回Number的方法:

public Number returnANumber() {
    ...
}

returnANumber方法能夠返回ImaginaryNumber而不是ObjectImaginaryNumber是一個Number,由於它是Number的子類,可是,Object不必定是Number — 它能夠是String或其餘類型。

你能夠重寫方法並定義它以返回原始子類的方法,以下所示:

public ImaginaryNumber returnANumber() {
    ...
}

這種稱爲協變返回類型的技術意味着容許返回類型在與子類相同的方向上變化。

注意:你還可使用接口名稱做爲返回類型,在這種狀況下,返回的對象必須實現指定的接口。

使用this關鍵字

在實例方法或構造函數中,這是對當前對象的引用 — 正在調用其方法或構造函數的對象,你可使用此方法從實例方法或構造函數中引用當前對象的任何成員。

將this與字段一塊兒使用

使用this關鍵字的最多見緣由是由於字段被方法或構造函數參數遮蔽。

例如,Point類就是這樣寫的:

public class Point {
    public int x = 0;
    public int y = 0;
        
    //constructor
    public Point(int a, int b) {
        x = a;
        y = b;
    }
}

但它多是這樣寫的:

public class Point {
    public int x = 0;
    public int y = 0;
        
    //constructor
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

構造函數的每一個參數都會影響對象的一個​​字段 — 構造函數內部的x是構造函數的第一個參數的本地副本,要引用Point字段x,構造函數必須使用this.x

將this與構造函數一塊兒使用

在構造函數中,你還可使用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;
    }
    ...
}

該類包含一組構造函數,每一個構造函數初始化一些或全部矩形的成員變量,構造函數爲任何成員變量提供默認值,其初始值不是由參數提供的。例如,無參數構造函數在座標0,0處建立1x1矩形。雙參數構造函數調用四參數構造函數,傳遞寬度和高度,但始終使用0,0座標,和以前同樣,編譯器根據參數的數量和類型肯定要調用的構造函數。

若是存在,則另外一個構造函數的調用必須是構造函數中的第一行。

控制對類成員的訪問

訪問級別修飾符肯定其餘類是否可使用特定字段或調用特定方法,訪問控制有兩個級別:

  • 在頂級 — publicpackage-private(沒有顯式修飾符)。
  • 在成員級別 — publicprivateprotectedpackage-private(無顯式修飾符)。

可使用修飾符public聲明一個類,在這種狀況下,該類對於全部類均可見,若是一個類沒有修飾符(默認,也稱爲包私有),它只在本身的包中可見(包是相關類的命名組 — 你將在後面的課程中瞭解它們)。

在成員級別,你也可使用public修飾符或無修飾符(package-private),就像使用頂級類同樣,而且具備相同的含義。對於成員,還有兩個額外的訪問修飾符:privateprotectedprivate修飾符指定只能在其本身的類中訪問該成員,protected修飾符指定只能在其本身的包中訪問該成員(與package-private同樣),此外,還能夠在另外一個包中經過其類的子類訪問該成員。

下表顯示了每一個修飾符容許的成員訪問權限。

修飾符 子類 全部
public Y Y Y Y
protected Y Y Y N
無修飾符 Y Y N N
private Y N N N

第一個數據列指示類自己是否能夠訪問由訪問級別定義的成員,如你所見,類始終能夠訪問本身的成員,第二列指示與該類相同的包中的類(無論父子關係)能夠訪問該成員,第三列指示在此包外聲明的該類的子類是否能夠訪問該成員,第四列指示是否全部類均可以訪問該成員。

訪問級別以兩種方式影響你,首先,當你使用來自其餘源的類(例如Java平臺中的類)時,訪問級別將肯定你本身的類可使用的那些類的哪些成員,其次,當你編寫一個類時,你須要肯定每一個成員變量和類中的每一個方法應具備的訪問級別。

讓咱們看一下類的集合,看看訪問級別如何影響可見性,下圖顯示了此示例中的四個類以及它們之間的關係。

classes-access.gif

下表顯示了Alpha類的成員對於可應用於它們的每一個訪問修飾符的可見性。

修飾符 Alpha Beta Alphasub Gamma
public Y Y Y Y
protected Y Y Y N
無修飾符 Y Y N N
private Y N N N

選擇訪問級別的提示:

若是其餘程序員使用你的類,你但願確保不會發生濫用錯誤,訪問級別能夠幫助你執行此操做。

  • 使用對特定成員有意義的最嚴格的訪問級別,除非你有充分的理由不使用private
  • 避免除常量以外的public字段(本教程中的許多示例都使用public字段,這可能有助於簡明地說明一些要點,但不建議用於生產代碼),public字段傾向於將你連接到特定實現,並限制你更改代碼的靈活性。

瞭解類成員

在本節中,咱們將討論使用static關鍵字建立屬於類的字段和方法,而不是類的實例。

類變量

當從同一個類藍圖建立許多對象時,它們每一個都有本身不一樣的實例變量副本,在Bicycle類的狀況下,實例變量是cadencegearspeed,每一個Bicycle對象都有這些變量本身的值,這些變量存儲在不一樣的內存位置。

有時,你但願擁有全部對象共有的變量,這是經過static修飾符完成的,在聲明中具備static修飾符的字段稱爲靜態字段或類變量,它們與類相關聯,而不是與任何對象相關聯,該類的每一個實例共享一個類變量,該變量位於內存中的一個固定位置,任何對象均可以更改類變量的值,但也能夠在不建立類實例的狀況下操做類變量。

例如,假設你要建立多個Bicycle對象併爲每一個對象分配一個序列號,從第一個對象開始爲1,此ID號對於每一個對象都是惟一的,所以是一個實例變量。同時,你須要一個字段來跟蹤已建立的Bicycle對象的數量,以便你知道要分配給下一個對象的ID,這樣的字段與任何單個對象無關,而與整個類有關,爲此,你須要一個類變量numberOfBicycles,以下所示:

public class Bicycle {
        
    private int cadence;
    private int gear;
    private int speed;
        
    // add an instance variable for the object ID
    private int id;
    
    // add a class variable for the
    // number of Bicycle objects instantiated
    private static int numberOfBicycles = 0;
        ...
}

類變量由類名自己引用,如:

Bicycle.numberOfBicycles

這清楚地代表它們是類變量。

注意:你也可使用對象引用來引用靜態字段 myBike.numberOfBicycles,但這是不鼓勵的,由於它沒有說明它們是類變量。

你可使用Bicycle構造函數來設置id實例變量並增長numberOfBicycles類變量:

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;

        // increment number of Bicycles
        // and assign ID number
        id = ++numberOfBicycles;
    }

    // new method to return the ID instance variable
    public int getID() {
        return id;
    }
        ...
}

類方法

Java編程語言支持靜態方法以及靜態變量,靜態方法在其聲明中具備static修飾符,應該使用類名調用,而不須要建立類的實例,如:

ClassName.methodName(args)
注意:你也可使用對象引用來引用靜態方法 instanceName.methodName(args),但這是不鼓勵的,由於它沒有說明它們是類方法。

靜態方法的常見用途是訪問靜態字段,例如,咱們能夠向Bicycle類添加一個靜態方法來訪問numberOfBicycles靜態字段:

public static int getNumberOfBicycles() {
    return numberOfBicycles;
}

並不是全部實例和類變量和方法的組合都是容許的:

  • 實例方法能夠直接訪問實例變量和實例方法。
  • 實例方法能夠直接訪問類變量和類方法。
  • 類方法能夠直接訪問類變量和類方法。
  • 類方法不能直接訪問實例變量或實例方法 — 它們必須使用對象引用,此外,類方法不能使用this關鍵字,由於沒有要引用的實例。

常量

static修飾符與final修飾符結合使用,也用於定義常量,final修飾符表示此字段的值不能更改。

例如,如下變量聲明定義了一個名爲PI的常量,其值是pi的近似值(圓周長與直徑之比):

static final double PI = 3.141592653589793;

以這種方式定義的常量不能從新分配,若是你的程序嘗試這樣作,則它是編譯時錯誤,按照慣例,常量值的名稱拼寫爲大寫字母,若是名稱由多個單詞組成,則單詞由下劃線(_)分隔。

注意:若是將基本類型或字符串定義爲常量而且該值在編譯時已知,則編譯器會將代碼中的常量名稱替換爲其值,這稱爲編譯時常量。若是外部世界中常量的值發生變化(例如,若是立法規定 pi實際上應該是 3.975),則須要從新編譯使用此常量來獲取當前值的任何類。

Bicycle類

在本節中進行了全部修改以後,Bicycle類如今是:

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

    public int getID() {
        return id;
    }

    public static int getNumberOfBicycles() {
        return numberOfBicycles;
    }

    public int getCadence() {
        return cadence;
    }
        
    public void setCadence(int newValue) {
        cadence = newValue;
    }
        
    public int getGear(){
        return gear;
    }
        
    public void setGear(int newValue) {
        gear = newValue;
    }
        
    public int getSpeed() {
        return speed;
    }
        
    public void applyBrake(int decrement) {
        speed -= decrement;
    }
        
    public void speedUp(int increment) {
        speed += increment;
    }
}

初始化字段

如你所見,你一般能夠在其聲明中爲字段提供初始值:

public class BedAndBreakfast {

    // initialize to 10
    public static int capacity = 10;

    // initialize to false
    private boolean full = false;
}

當初始化值可用而且初始化能夠放在一行上時,這頗有效,然而,這種形式的初始化因爲其簡單性而具備侷限性,若是初始化須要一些邏輯(例如,錯誤處理或for循環來填充複雜數組),則簡單的賦值是不合適的。實例變量能夠在構造函數中初始化,其中可使用錯誤處理或其餘邏輯,爲了爲類變量提供相同的功能,Java編程語言包括靜態初始化塊。

注意:沒有必要在類定義的開頭聲明字段,儘管這是最多見的作法,只有在使用它們以前才須要聲明和初始化它們。

靜態初始化塊

靜態初始化塊是用大括號{}括起來的常規代碼塊,前面是static關鍵字,這是一個例子:

static {
    // whatever code is needed for initialization goes here
}

一個類能夠有任意數量的靜態初始化塊,它們能夠出如今類體中的任何位置,運行時系統保證按照它們在源代碼中出現的順序調用靜態初始化塊。

還有靜態塊的替代方法 — 你能夠編寫私有靜態方法:

class Whatever {
    public static varType myVar = initializeClassVariable();
        
    private static varType initializeClassVariable() {

        // initialization code goes here
    }
}

私有靜態方法的優勢是,若是須要從新初始化類變量,能夠在之後重用它們。

初始化實例成員

一般,你可使用代碼在構造函數中初始化實例變量,使用構造函數初始化實例變量有兩種選擇:初始化塊和final方法。

實例變量的初始化程序塊看起來就像靜態初始化程序塊,但沒有static關鍵字:

{
    // whatever code is needed for initialization goes here
}

Java編譯器將初始化程序塊複製到每一個構造函數中,所以,該方法可用於在多個構造函數之間共享代碼塊。

沒法在子類中重寫final方法,這在接口和繼承的課程中討論,如下是使用final方法初始化實例變量的示例:

class Whatever {
    private varType myVar = initializeInstanceVariable();
        
    protected final varType initializeInstanceVariable() {

        // initialization code goes here
    }
}

若是子類可能想要重用初始化方法,這尤爲有用,該方法是final,由於在實例初始化期間調用非final方法可能會致使問題。

建立和使用類和對象的總結

類聲明爲類命名,並將類主體括在大括號之間,類名能夠在前面加上修飾符,類主體包含類的字段、方法和構造函數,類使用字段來包含狀態信息,並使用方法來實現行爲,初始化類的新實例的構造函數使用類的名稱,看起來像沒有返回類型的方法。

你能夠經過相同的方式控制對類和成員的訪問:在聲明中使用諸如public之類的訪問修飾符。

你能夠經過在成員聲明中使用static關鍵字來指定類變量或類方法,未聲明爲static的成員隱式地是實例成員,類變量由類的全部實例共享,能夠經過類名和實例引用來訪問,類的實例獲取每一個實例變量的本身的副本,必須經過實例引用訪問它們。

你可使用new運算符和構造函數從類建立對象,new運算符返回對已建立對象的引用,你能夠將引用分配給變量或直接使用它。

能夠經過使用限定名稱來引用可在其聲明的類以外的代碼訪問的實例變量和方法,實例變量的限定名稱以下所示:

objectReference.variableName

方法的限定名稱以下所示:

objectReference.methodName(argumentList)

或者:

objectReference.methodName()

垃圾收集器自動清理未使用的對象,若是程序再也不包含對它的引用,則不使用該對象,你能夠經過將包含引用的變量設置爲null來顯式刪除引用。


上一篇: 對象

下一篇:嵌套類

相關文章
相關標籤/搜索