Java程式設計(2021春)——第二章筆記與思考
本章概覽:
物件導向方法的特徵
抽象:從同型別物件中抽象出共同屬性
封裝:把資料和處理資料的方法封到一個類中
繼承:在已有的類的基礎上開發新的類
多型:在由繼承的環境下,超類(父類)和子類都能響應共同的訊息,但是響應訊息的具體實現辦法可以不同
類與物件基礎
類的宣告
物件的建立
資料成員
方法成員
包
類的訪問許可權控制
類成員的訪問許可權控制
物件初始化和回收
構造方法(初始化)
記憶體回收
列舉型別
簡單列舉型別
列舉類(功能更為強大)
應用舉例
銀行賬號示例
2.1 物件導向方法的特性
抽象
抽象的思想:忽略問題中與當前目標無關的方面,只關注與當前目標有關的內容。
封裝
封裝是一種資訊隱蔽技術。利用抽象資料型別將資料和基於資料的操作封裝在一起;使用者只能看到物件的封裝介面資訊,物件的內部細節對使用者是隱蔽的;封裝的目的在於將物件的使用者和設計者分開,使用者不必知道行為實現的細節。
繼承
繼承是一種基於已有類產生新類的機制。是指新的類可以獲得已有類(成為超類、基類或父類)的屬性和行為,稱新類為已有類的子類(也成為派生類),在Java中一般用超類、子類的術語;在繼承過程中子類繼承了超類的特性,包括方法和例項變數;子類也可以修改繼承的方法或增加新的方法;有助於解決軟體的可重用性問題,使程式結構清晰,降低了編碼和維護的工作量。
單繼承
一個子類只有單一的直接超類。
多繼承
一個子類可以有一個以上的直接超類。
在Java中僅支援單繼承。
多型
在有繼承的情況下,超類和他的子類都可以響應同名的訊息,但是這些物件對這些同名的訊息的實現方式可以是不一樣的。主要通過子類覆蓋從超類繼承過來的方法來實現多型。
2.2-1 類宣告與物件建立
類與物件的關係
類是對一類物件共同的屬性和行為的一種抽象,是一種抽象出來的資料型別;
物件是類的具體的例項
類宣告
/*完整語法結構*/
/*方括號裡的關鍵字是可選項,可有可無*/
[public][abstract|final]class類名稱//class關鍵字是必須的,表示後面定義的是一個類
[extends父類名稱]
[implements介面名稱列表]//大括號中為類體
{
資料成員宣告及初始化;
方法宣告及方法體;
}
class
表明其後宣告的是一個類。extends
如果所宣告的類是從某一父類派生而來,那麼,父類的名字應該寫在extends
之後。即,當我們要繼承已有的類,形成新類時,要用extends
關鍵字implements
(用來實現介面)如果所宣告的類要實現某些介面,那麼,介面的名字應寫在implements
之後。public
表明此類為公有類(後續章節介紹類的訪問控制屬性時會介紹public
)。abstract
是抽象的意思,有abstract
修飾的類是抽象類(後續章節會介紹)。final
表明這個類是終結類,表明這個類不可以被繼承。
物件引用宣告
語法
類名 引用變數名;
例:
Clock
是已經宣告的類名,宣告引用變數aclock
,勇於儲存該物件的引用。
Clock aclock;
此時,物件還沒有生成,我們只是建立了一個引用,且為空引用。
物件的建立
語法
意思是分配新的記憶體空間(在執行時分配),勇於存放一個Clock
型別的物件。此時沒有進行初始化,如果希望初始化,則需要在圓括號中給出初始化引數(後續會介紹)
new <類名>()
例:
aclock = new Clock();
new
的作用是在記憶體中為Clock
型別的物件分配記憶體空間,同時返回物件的引用。
醫用變數可以被賦以空值,如
aclock = null;
2.2-2 資料成員
資料成員用來表示物件的狀態,也可以存放在整個類所有物件之間要共享的資料;資料成員可以是任意的資料型別,如基本型別,另外一個類的物件,陣列等。
語法形式
方括號中為可選項,在需要的時候寫,不需要的時候可以不寫。
[public|protected|private]
[static][final][transient][volatile]
資料型別 變數名1[=變數初值],變數名2[=變數初值],...;
說明
- 資料型別必須說明,可以是基本型別,也可以是類型別,還可以是陣列(陣列也是物件)。
public
protected
private
稱為訪問控制符,是用來控制對類成員的訪問許可權的。static
指明這是一個靜態成員變數(類變數)(後面會介紹)。final
指明變數的值不可以被修改。transient
指明變數不需要序列化(後面介紹檔案IO時會涉及到)。volatile
指明變數是共享變數。
例項變數
-
沒有
static
修飾的變數(資料成員)成為例項變數。 -
例項變數,也叫屬於物件的屬性(例項屬性),是用來描述每個物件的屬性的,不同物件的屬性即例項變數的值往往是不同的,這些值用來區分此物件與彼物件。
-
訪問例項變數要通過變數名訪問,語法形式為
<例項名>.<例項變數名>
。不是所有例項變數都可以這樣訪問,要注意屬性或變數的訪問控制許可權。
例1:圓類
/*圓類儲存在檔案Circle.java中,測試類儲存在檔案ShapeTester.java中,兩檔案放在相同的目錄下*/
public class Circle{
int radius;
}
public class ShapeTester{
public static void main(String args[]){//定義了一個主方法
Circle x;//定義了一個圓類的引用
x = new Circle();//用new獲得一個新的圓物件,並把引用賦給x
System.out.println(x);
System.out.println("radius = " + x.radius);
}
}
輸出結果(在本機測試,與網課中不同)
Circle@379619aa
radius = 0
對輸出結果的說明:
所有的類中都有預設的toString()
方法,預設的toString
的返回:getClass().getName()+"@"+Integer.toHexString(hashCode())
,即,先取得類的類名並轉成字串,輸出@
符號,然後呼叫hashCode()
方法,將物件的雜湊碼轉成十六進位制形式的字串。
該預設的toString
不是很有意義,後續章節將會介紹如何自己寫一個toString
覆蓋已有的toString
。
例2:矩形類
/*矩形類儲存在Recrangle.java中,測試類儲存在ShapeTester.java中,兩檔案儲存在相同目錄下*/
public class Rectangle {
double width = 10.128;//在類裡面已經定義好初始值了
double height = 5.734;//在類裡面已經定義好初始值了
}
public class ShapeTester{
public static void main(String args[]){//定義了一個主方法
Circle x;
Rectangle y;
x = new Circle();//圓物件依然沒初始化(和上方程式碼中相同)
y = new Rectangle();
System.out.println(x + " " + y);
}
}
輸出結果
Circle@cac736f hello.Rectangle@5e265ba4
類變數
- 整個類 所有物件 共享的資料稱作類變數(靜態變數)。
- 用
static
修飾。 - 在整個類中只有一個值,儲存一份即夠。
- 類初始化的同時就被賦值。
- 使用情況:類中所有物件都相同的屬性;需要經常共享的資料;系統中用到的一些常量值。
- 引用形式:
<類名|例項名>.<類變數名>
,無需用物件名使用,但是用物件名使用也可。
例3:具有類變數的圓類
public class Circle{
static double PI = 3.14159265;//類變數(靜態變數)圓裡面所有物件都共享常量pi
int radius;
}
//當我們生成Circle類的例項時,在每一個例項中並沒有儲存PI的值,PI的值儲存在類中(只存一份)
對類變數進行測試
public class ClassVariableTester {
public static void main(String[] args) {
Circle x = new Circle();//構造圓物件,並把引用賦給x
System.out.println(x.PI);//通過<例項名>.<類變數名>輸出PI的值
System.out.println(Circle.PI);//通過<類名>.<類變數名>輸出PI的值
Circle.PI = 3.14;
System.out.println(x.PI);
System.out.println(Circle.PI);
}
}
輸出結果如下
3.14159265
3.14159265
3.14
3.14
由以上測試可以看出,物件名訪問和類名訪問靜態成員的時候是一樣的
小結
此部分涉及的新名詞較多,需要著重辨析不同名詞所指代內容是否相同,及時予以總結。
2.2-3 方法成員
類定義的方法分為兩類,類的方法和例項的方法。
類的方法是用來表示類的一些共同的行為或功能的。
例項的方法是用來表示每一個例項的功能或者行為的。
語法形式
在類中定義方法和C語言中定義函式很相像,只不過方法不是獨立的,即不是全域性的,是必須出現在類體裡面的。
/*方括號中為可選內容*/
[public|protected|private]
[static][final][abstract][native][synchronized]
返回型別 方法名([引數列表])[throws exceptionList]//返回型別類似C中返回值型別,方法名類似C中函式名,引數列表類似C中函式形參表
{
方法體;//類似C中函式體
}
public
protected
private
控制訪問許可權。static
指明這是一個類方法(靜態方法)。final
指明這是一個終結方法。abstract
指明這是一個抽象方法(只有方法原型,沒有方法體體現)。native
用來整合java
程式碼和其他語言的程式碼(本課程不涉及)。synchronized
用來控制多個併發執行緒對共享資料的訪問(在Java語言程式設計進階中涉及)。- 返回型別:方法返回值的型別們可以是任意的Java資料型別;當不需要返回值時,返回型別為void。
- 引數型別:簡單資料型別、引用型別(陣列、類、介面);可以有多個引數,也可以沒有引數,方法宣告時的引數稱為形式引數。
- 方法體:方法體的實現;包括區域性變數的宣告以及所有合法的Java語句;區域性變數的作用域只限制在該方法體內部。
throw exceptionList
列出這個方法有可能丟擲的異常,即異常丟擲列表(在後續章節會介紹異常處理)
例項方法
例項方法屬於每個物件,用來表示每個物件的功能或者行為。定義例項方法時不用static
關鍵字。
例項方法呼叫
給物件發訊息,使用物件的某個行為/功能時呼叫方法(因為方法即代表物件的行為或者功能)。
語法
例項方法呼叫格式
<物件名>.<方法名>([引數列表])
<物件名>
為訊息的接收者。
從內外來呼叫方法時通過物件名來呼叫;如果在類體裡面方法之間互相呼叫,前面則不需要掛一個物件名,即在類體內部方法與方法之間可以直接互相呼叫,直接用方法名即可。
引數傳遞
值傳遞:引數型別為基本資料型別時,用實參初始化形參,實際上是一次性的單向傳遞,然後實參和形參之間沒有關係了。
引用傳遞:引數型別為物件型別或陣列時,傳物件作為引數,實際上傳的是物件的引用,實參名和形參名兩個不同的名字指向了同一個物件。
例:具有例項方法的圓類
public class Circle{
static double PI = 3.14159265;
int radius;
public double circumference() {//求圓周長的方法
return 2 * PI * radius;
}
public void enlarge(int factor) {//將圓擴大若干倍的方法,引數是倍數
radius = radius * factor;
}
public boolean fitsInside(Rectangle r) {//引數是另一個類的物件,方法要計算是否可以將圓裝入矩形並返回布林值
return (2 * radius < r.width) && (2 * radius < r.height);
}
}
測試如下:
public class InsideTester {
public static void main(String[] args) {
Circle c1 = new Circle();
c1.radius = 8;
Circle c2 = new Circle();
c2.radius = 15;
Rectangle r = new Rectangle();
r.width = 20;
r.height = 30;
System.out.println("Circle 1 fits inside Rectangle:" + c1.fitsInside(r));
System.out.println("Circle 2 fits inside Rectangle:" + c2.fitsInside(r));
}
}
執行結果如下:
Circle 1 fits inside Rectangle:true
Circle 2 fits inside Rectangle:false
類方法(靜態方法)
- 類方法用來表示類裡所有物件的共同行為。
- 類方法也成為靜態方法,宣告前需加
static
修飾。 - 不能被宣告為抽象方法。
- 可以通過類名直接呼叫,也可以通過類例項呼叫。
例 :溫度轉換
只需要方法,不需要物件。
public class Converter {
public static int centigradeToFahrenheit(int cent) {
return (cent * 9 / 5 + 32);
}
}
方法呼叫
Converter.contigradeToFahrenheit(10)
可變長引數
方法引數列表中可以定義可變長引數列表。
- 可變長引數使用省略號表示,其實質是陣列,例如
String...s
表示String[] s
。 - 對於具體可變長引數的方法,傳遞給可變長引數的實際引數可以是0到多個物件。
例:可變長引數
static double maxArea(Circle c,Rectangle...varRec){
Rectangle[] rec = varRec;
for(Rectangle r:rec){//基於範圍的for迴圈,定義一個Rectangle 的引用,冒號後面是 陣列名,這個迴圈的作用是在迴圈每一次依次從陣列中取出一個元素,賦給r,來訪問這個元素。此方法也是訪問可變長引數的常用手段
//...(沒有具體實現方法體,只是做一個示意)
}
}
參數列中有一個圓物件的引用做引數,有若干個矩形物件的引用做引數,其中...
表示varRec
本質上是一個Rectangle
物件的引用陣列,只是陣列元素數不確定,具體呼叫形式如下。
public static void main(String[] args){
Circle c = new Circle();
Rectangle r1 = new Rectangle();
Rectangle r2 = new Rectangle();
System.out.println("max area of c,r1 and r2 is " + maxArea(c,r11,r2));
System.out.println("max area of c and r1 is " + maxArea(c,r1));
System.out.println("max area of c and r2 is " + maxArea(c,r2));
System.out.println("max area of only c is" + maxArea(c));
}
2.2-4 包
包是一組類的集合;一個包可以包含若干個類檔案,還可以包含若干個包。
包的作用
- 將相關的原始碼檔案組織在一起
- 類名的空間管理,利用包來劃分空間可以避免類名衝突(程式規模比較大的時候,還要用到很多域地域庫的時候,可能會發生重名,此時利用包來劃名字空間,將一組一組功能相關的類放在一個包裡)
- 提供包一級的封裝及存取許可權。
包的命名
- 每個包的名稱必須是獨一無二的(包名不重)。
- Java中包名使用小寫字母表示。
- Java建議的命名方式為將機構的Internet域名反序作為包名的前導;若包名中有任何不可用於識別符號的字元,用下劃線替代;若包名中的任何部分與關鍵字衝突,字尾下劃線;若包名中的任何部分以數字或其他不能用作識別符號起始的字元開頭,字首下劃線。
編譯單元
- 一個Java原始碼檔案稱為一個編譯單元,由三部分組成:①所屬包的宣告(省略則屬於預設包)②
Import
(引入)包的宣告,用於匯入外部的類(使用其他包裡面的類)③(自己定義的)類和介面的宣告。 - 一個編譯單元中只能有一個
public
類,該類名與檔名相同,編譯單元中的其他類往往是public
類的輔助類,經過編譯,每個類都會產生一個class
檔案,檔名必須是相同的;輔助的類不叫public
類,叫預設的default
類,在內部起輔助作用。
包的宣告
命名的包(Named Packages)
例如:package Mypackage;
預設包(未命名的包)
不含有包宣告的編譯單元是預設包的一部分。
包與目錄
- 包名就是資料夾名,即目錄名(每個包對應一個目錄即資料夾)。
- 目錄名不一定是包名(每個目錄不一定對應一個包)。
引入包
-
引用包是為了使用包所提供的類,此時需要使用
import
語句引入所需要的類。 -
Java編譯器回味所有程式自動引入包
java.lang
。 -
引入更多其它包時:
import
語句的格式:import package1[.package2...].(classname|*);
包名可以是多級,由上文介紹Java推薦包命名規則知域名反序形如
.packagei
;如果要引入包的某個類名,就將類名寫在這classname
,如果要引入包裡面所有的類,則可以使用*
代替類名。
靜態引入
在內外使用一個類的靜態成員,我們的引用方法是類名.靜態方法名
來使用,如果大量使用某些靜態方法,可以用靜態引入簡化之。
-
單一引入是指引入某一個指定的靜態成員,例如
import static java.lang.Math.PI;
引入了PI
常量。 -
全體引入是指引入類中所有的靜態成員,例如
import static java.lang.Math.*;
-
例如
import static java.lang.Math.PI; public class Circle{ int radius; public double circumference(){ return 2 * PI * radius;//此時可以直接使用PI而不用帶著類名,方便。 } }
2.2-5 類的訪問許可權控制
類在不同範圍是否可以被訪問
型別 | 無修飾(預設) | public |
---|---|---|
同一包中的類 | 是 | 是 |
不同包中的類 | 否 | 是 |
類的成員訪問許可權控制
公有(public
)
可以被其他任何方法訪問(前提是對類成員所屬的類有訪問許可權)。
保護(protected
)
只可被同一類及其子類的方法訪問。
私有(private
)
只可被同一類的方法訪問。
預設(default
)
僅允許同一個包內的訪問;又被稱為包(package
)訪問許可權。
類成員在不同範圍是否可以被訪問
以下前提為該類可以被訪問
型別 | private | 無修飾 | protected | public |
---|---|---|---|---|
同一類 | 是 | 是 | 是 | 是 |
同一包中的子類 | 否 | 是 | 是 | 是 |
同一包中的非子類 | 否 | 是 | 是 | 是 |
不同包中的子類 | 否 | 否 | 是 | 是 |
不同包中的非子類 | 否 | 否 | 否 | 是 |
即,在該類可以被訪問的情況下,public
公有成員都可以被訪問;protected
保護乘員主要在有繼承關係的時候,可以被子類的其他方法訪問,無論子類和超類是否在同一個包中;無修飾(預設)訪問控制許可權是包內的,即同一個類中的其他方法可以訪問這種無修飾的資料成員和方法成員;private
最為嚴格,只可以被同一個類中的其他方法訪問,其他類中不可以看到別的類中的private
成員,無論是否在同一個包中。
例:改進的圓類
public class Circle{
static double PI = 3.14159265;
private int radius;//將半徑設為私有
public double circumference() {
return 2 * PI * radius;
}
}
再編譯CircumferenceTester.java
public class CircumferenceTester{
public static void main(String[] args){
Circle c1 = new Circle();
c1.radius = 50;
Circle c2 = new Circle();
c2.radius = 10;
double circum1 = c1.circumference();
double circum2 = c2.circumference();
System.out.println("Circle 1 has circumference " + circum1);
System.out.println("Circle 2 has circumference " + circum2);
}
}
上述測試類會出現語法錯誤提示The field Circle.radius is not visible
,原因是類的private
私有成員在其它類中不能直接訪問,後續會介紹如何通過公有介面訪問私有成員(在類中提供公有介面,一般叫get
方法和set
方法,get
方法用於獲取資料成員的值(屬性),set
方法用於升格至資料成員的值(屬性))。
get
方法(public
)
-
功能是取得屬性變數的值。
-
get方法名以
get
開頭,後面跟例項變數的名字。例如:
public int getRadius(){ return radius; }
以上例項中
getRadius
中的R
大寫,為一般慣例(老師語)。筆者感覺像是駝峰命名法的應用(上學期一直這麼用來著(逃
set
方法(public
)
-
功能是修改屬性變數的值。
-
set
方法名以set
開頭,後面是例項變數的名字。例如:
public void setRadius(int r){ radius = r; }
this
關鍵字
如果方法內的區域性變數(包括形參)名與例項變數名相同,則方法體內訪問例項變數時需要this
關鍵字。
例如:
public void setRadius(int radius){
this.radius = radius;
}
Tip:在set
方法中將形參名和要設定的變數名相同是一種常用的處理方式,可以使得set
方式的可讀性很好。
2.3-1 物件初始化
當我們定義基本型別變數的時候往往會希望在定義變數的同時指定初始值,叫做變數的初始化;當我們構造物件的時候,也希望給物件指定一個初始狀態。
- 物件初始化:系統在生成物件時,會為物件分配記憶體空間,並自動呼叫構造方法對例項變數進行初始化。其中構造方法是我們自己寫的描述如何對物件進行初始化的方法,即描述初始化的演算法。
- 物件回收:物件不再使用時,系統會呼叫垃圾回收程式將其佔用的記憶體回收(下一節介紹)。
物件的初始化不會由編譯器自動完成,這有別於基本型別的變數初始化。
構造方法
- 方法名與類名相同。
- 不定義返回型別,也不能寫
void
。 - (大多數情況下)會被宣告為公有的(
public
),也存在一些特殊場合我們不希望物件被隨意構造時,也可能不宣告為公有的(初學者無需考慮)。 - 是類的方法成員,可以有多個引數且可以有任意多個引數。
- 主要作用是完成物件的初始化,即,不要在構造方法中寫過多的其他功能。
- 不能在程式中顯示地呼叫。
- 在生成一個物件時,會自動呼叫該類的構造方法為新物件初始化。
- 每個類都有且必須有構造方法,如果我們沒有顯示地宣告構造方法,編譯器也不會報錯,會隱含生成預設的構造方法。
預設構造方法
- 沒有引數(內部類除外),方法體為空(內部類會在後續介紹)。
- 使用預設的構造方法初始化物件時,如果在類宣告沒有給例項變數賦初值,則物件的屬性值為空或零。
例:一個銀行賬戶類及測試程式碼
銀行賬戶類
public class BankAccount {
String ownerName;
int accountNumber;
float balance;//餘額
}
測試
public class BankTester {
public static void main(String[] args) {
BankAccount myAccount = new BankAccount();
System.out.println("ownerName=" + myAccount.ownerName);
System.out.println("accountNumber=" + myAccount.accountNumber);
System.out.println("balabne=" + myAccount.balance);
}
}
輸出結果
ownerName=null
accountNumber=0
balabne=0.0
從輸出結果可以看到,引用型別初始值為空引用,賬戶(數值)初始值為0,餘額(數值)初始值為0.0。
自定義構造方法與過載
- 在生成物件時給構造方法傳送初始值,為物件進行初始化。
- 構造方法可以被過載:一個類中可以有兩個及以上同名的方法,但參數列不同,這種情況被稱為過載;在方法呼叫時,可以通過引數列表的不同來辨別應呼叫哪一個方法。
- 只要顯示宣告瞭構造方法,編譯器就不再生成預設的構造方法。
- 也可以顯示宣告無引數的構造方法,方法體中可以定義預設初始化方式(相當於構造出了預設構造方法,此時允許我們對物件不給引數初始化,而不是不進行初始化)。
例:為銀行賬戶類宣告構造方法
public class BankAccount {
String ownerName;
int accountNumber;
float balance;
/* 為BankAccount宣告一個有三個引數的構造方法 */
public BankAccount(String initName, int initAccountNumber, float initBalance) {
ownerName = initName;
accountNumber = initAccountNumber;
balance = initBalance;
}
/* 假設一個新賬號的初始餘額可以為0,則可以增加一個帶有兩個引數的構造方法 */
public BankAccount(String initName, int initAccountNumber) {
ownerName = initName;
accountNumber = initAccountNumber;
balance = 0.0f;
}
/* 無引數的構造方法——自定義預設的初始化方法 */
public BankAccount() {
ownerName = "";
accountNumber = 999999;
balance = 0.0f;
}
}
以上構造方法中的邏輯本質相同,只是引數的個數不同,此時,可以採用如下方法減少冗餘。
宣告構造方法時使用this
關鍵字
- 可以使用
this
關鍵字在一個構造方法中呼叫另外的構造方法。 - 程式碼更簡潔,維護起來更容易。
- 通常用引數個數較少的構造方法呼叫引數個數最多的構造方法。
例:使用this
的過載構造方法
public BankAccount() {
this("", 999999, 0.0f);//this代表本類的引數方法引數名,把引數作為實參
}
public BankAccount(String initName, int initAccountNumber) {
this(initName, initAccountNumber, 0.0f);
}
public BankAccount(String initName, int initAccountNumber, float initBalance) {
ownerName = initName;
accountNumber = initAccountNumber;
balance = initBalance;
}
final
變數的初始化
如果我們希望某個屬性一經初始化就不能再被改變,即為常量,則可以使用final
。
- 例項變數和類變數都可以被宣告為
final
。 final
例項變數可以在類中定義時給出初始值,或者在每個構造方法結束之前完成初始化(最晚的時刻)。- 一旦構造方法執行結束,
final
變數的值就不能再被改變。 final
類變數必須在宣告的同時完成初始化:因為類變數屬於整個類,在整個類中只有一份,不屬於任何物件。同樣地,宣告完成後即不能再被改變。
2.3-2 記憶體回收
物件的自動回收
- 無用物件:離開了作用域的物件;無引用指向的物件(無需擔心記憶體洩漏的問題)。
- Java執行時系統通過垃圾收集器週期性地釋放無用物件所使用的記憶體。
- Java執行時系統會在對物件進行自動垃圾回收之前(的最後一刻),自動呼叫物件的
finalize()
方法(每個類中預設都有finalize()
方法,這個方法也可以被覆蓋)。
垃圾收集器
- 自動掃描物件的動態記憶體區,對不再使用的物件做上標記以進行垃圾回收。
- 作為一個後臺執行緒執行,通常在系統空閒時非同步地執行。
finalize()
方法
- 在類
java.lang.Object
中宣告,因此Java中的每一個類都有該方法:protected void finalize() throws throwable
。java.lang.Object
是所有Java類的直接的或間接的超類,因此Java中每一個類都有該方法(從Object
繼承而來)。 - 用於釋放資源。
- 類可以覆蓋(重寫)
finalize()
方法。 finalize()
方法可能在任何時機以任何次序執行,所以如果要覆蓋finalize()
方法,釋放的操作不能有嚴格次序關係。
2.4 列舉類
宣告列舉類
[public]enum 列舉型別名稱
[implements 介面名稱列表]{
列舉值;
變數成員宣告及初始化;
方法宣告及方法體;
}
列舉類是一種類,也可以宣告為public
,不宣告則預設為包內的(見[2.2-5類的訪問許可權控制](##2.2-5 類的訪問許可權控制 ))
例:簡單的列舉型別
enum Score {
EXCELLENT, QUALIFIED, FAILED;
};
public class ScoreTester {
public static void main(String[] args) {
giveScore(Score.EXCELLENT);
}
public static void giveScore(Score s) {
switch (s) {
case EXCELLENT:
System.out.println("Excellent");
break;
case QUALIFIED:
System.out.println("Quqlified");
break;
case FAILED:
System.out.println("Failed");
break;
}
}
}
列舉類的特點
- 列舉定義實際上是定義了一個類。
- 所有列舉型別都隱含整合(擴充套件)自
java.lang.Enum
類,因此列舉型別不能再繼承其他任何類(繼承會在後續章節介紹)。 - 列舉型別的類體中可以包括方法和變數。
- 列舉型別的構造方法必須是包內私有或者私有的。定義在列舉開頭的常量會被自動建立,不能顯式地呼叫列舉類的構造方法。
列舉型別的預設方法
- 靜態的
values()
方法用於獲得列舉型別的列舉值的陣列,即values()
會返回一個陣列包含所有列舉值。 toString
方法返回列舉值的字串描述,即,將列舉值轉換成字串型別。valueOf
方法將以字串形式表示的列舉值轉化為列舉型別的物件。Ordinal
方法獲得物件在列舉型別中的位置索引。
例:
public enum Planet {//Planet類代表太陽系中行星的列舉型別
MERCURY(3.303e+23, 2.4397e6), VENUS(4.869e+24, 6.0518e6), EARTH(5.976e+24, 6.37814e6), MARS(6.421e+23, 3.3972e6),
JUPITER(1.9e+27, 7.1492e7), SATURN(5.688e+26, 6.0268e7), URANUS(8.686e+25, 2.5559e7), NEPTUNE(1.024e+26, 2.4746e7);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {//定義了構造方法,用引數方法初始化mass和radius
this.mass = mass;
this.radius = radius;
}
private double mass() {//返回mass值,私有方法
return mass;
}
private double radius() {//返回radius值,私有方法
return radius;
}
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;//靜態final常量
double surfaceGravity() {
return G * mass / (radius * radius);//處理列舉物件,說明列舉物件也可以有功能
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();//處理列舉物件
}
public static void main(String[] args) {//獲得命令列引數
if (args.length != 1) {
System.err.println("Usage: java Planet <earth_weight>");
System.exit(-1);
}
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight / EARTH.surfaceGravity();
for (Planet p : Planet.values())//專門處理陣列/集合型別的增強型迴圈,p是物件,是在定義列舉類的時候自動生成的
System.out.printf("Your weight on %s is %f%n", p, p.surfaceWeight(mass));
}
}
2.5 應用舉例
本章將會通過一個銀行賬戶類的例項複習學過的語法。
初步的BankAccount類——BankAccount.java
public class BankAccount {
private String ownerName;
private int accountNumber;
private float balance;
public BankAccount() {
this("", 0, 0);
}
public BankAccount(String initName, int initAccNum, float initBal) {
ownerName = initName;
accountNumber = initAccNum;
balance = initBal;
}
}
public String getOwnerName() {
return ownerName;
}
public int getAccountNumber() {
return accountNumber;
}
public float getBalance() {
return balance;
}
public void setOwnerName(String newName) {
ownerName = newName;
}
public void setAccountNumber(int newNum) {
accountNumber = newNum;
}
public void setBalance(float newBalance) {
balance = newBalance;
}
測試類——AccountTester.java
public class AccountTester {
public static void main(String[] args) {
BankAccount anAccount;
anAccount = new BankAccount("ZhangLi", 100023, 0);
anAccount.setBalance(anAccount.getBalance() + 100);
System.out.println("Here in the account: " + anAccount);
System.out.println("Account name: " + anAccount.getOwnerName());
System.out.println("Account number:" + anAccount.getAccountNumber());
System.out.println("Balance:$" + anAccount.getBalance());
}
}
輸出結果
Here in the account: BankAccount@379619aa
Account name: ZhangLi
Account number:100023
Balance:$100.0
以下將對銀行賬戶類進行修改並測試:
- 覆蓋
toString()
方法。 - 宣告存取款方法。
- 使用
Decimal Format
類。 - 宣告類方法生成特殊的例項。
- 宣告類變數。
1. 覆蓋toString()
方法
在每個類中預設都有一個toString()
方法,當我們在上下文需要一個字串String
型別的時候,如果我們給了一個類的物件,會自動呼叫類的toString()
方法。即,System.out.println(anAccount);
和System.out.println(anAccount.toString());
等價。
由於自帶的toString()
方法用處不大,我們如果需要特殊的轉換功能,則需要自己覆蓋toString()
方法,並需要遵循以下原則。
- 必須被宣告為
public
型別。 - 返回型別必須是
String
。 - 方法的名稱必須為
toString()
,且沒有引數。 - 在方法體中不要使用輸出方法
System.out.println()
。
為BankAccount
覆蓋toString()
方法
public String toString() {
return ("Account#" + accountNumber + " with balance $" + balance);
}
重新編譯BankAccount
類,並執行測試類BankAccountTester
,結果如下:
Here in the account: Account#100023 with balance $100.0
Account name: ZhangLi
Account number:100023
Balance:$100.0
2. 宣告存取款操作
銀行賬戶中的餘額不應當是隨意變動的,而是通過存取款操作來發生改變的。
給BankAccount
類增加存款及取款方法
//存錢
public float deposit(float anAmount) {
balance += anAmount;
return (balance);
}
//取錢
public float withdraw(float anAmount) {
balance -= anAmount;
return (balance);
}
測試存取款——修改AccountTester.java
public class AccountTester {
public static void main(String[] args) {
BankAccount anAccount;
anAccount = new BankAccount("ZhangLi", 100023, 0);
anAccount.setBalance(anAccount.getBalance() + 100);
System.out.println(anAccount);
System.out.println();
anAccount = new BankAccount("WangFang", 100024, 0);
System.out.println(anAccount);
anAccount.deposit(225.67f);
anAccount.deposit(300.00f);
System.out.println(anAccount);
anAccount.withdraw(400.17f);
System.out.println(anAccount);
}
}
測試結果
Account#100023 with balance $100.0
Account#100024 with balance $0.0
Account#100024 with balance $525.67
Account#100024 with balance $125.49997
3. DecimalFormat
類(格式化)
DecimalFormat
類在java.text
包中。- 在
toString()
方法中使用DecimalFormat
類的例項方法format
對資料進行格式化。
進一步修改toString
方法,給輸出金額設定格式
public String toString() {
return ("Account#" + accountNumber + " with balance" + new java.text.DecimalFormat("$0.00").format(balance));
}
更多內容關於DecimalFormat
的內容可以檢視Java的API文件,學會檢視文件是一個程式設計師的必備技能。
4. 使用類方法生成特殊的例項
例:使用靜態方法(類方法)生成三個樣例賬戶
public static BankAccount example1() {
BankAccount ba = new BankAccount();
ba.setOwnerName(("LiHong"));
ba.setAccountNumber(554000);
ba.deposit(1000);
return ba;
}
public static BankAccount example2() {
BankAccount ba = new BankAccount();
ba.setOwnerName("ZhaoWei");
ba.setAccountNumber(554001);
ba.deposit(1000);
ba.deposit(2000);
return ba;
}
public static BankAccount emptyAccountExample() {
BankAccount ba = new BankAccount();
ba.setOwnerName("HeLi");
ba.setAccountNumber(554002);
return ba;
}
5. 設定類變數
例:修改賬號生成、餘額變動方式
- 修改構造方法,取消賬戶引數。(賬號不應當是人為指定)
- 不允許直接修改賬號,取消
setAccountNumber
方法。 - 增加類變數
LAST_ACCOUNT_NUMBER
初始值為0,當生成一個新的BankAccount
物件時,其賬號(accountNumber
)自動設定為LAST_ACCOUNT_NUMBER
的值累加1。 - 取消
setBalance
方法,僅通過存取款操作改變餘額。
修改後完整的BankAccount2.java
package hello;
package hello;
public class BankAccount2 {
private static int LAST_ACCOUNT_NUMBER = 0;
private int accountNumber;
private String ownerName;
private float balance;
public BankAccount2() {
this("", 0);
}
public BankAccount2(String initName) {
this(initName, 0);
}
public BankAccount2(String initName, float initBal) {
ownerName = initName;
accountNumber = ++LAST_ACCOUNT_NUMBER;
balance = initBal;
}
public String getOwnerName() {
return ownerName;
}
public int getAccountNumber() {
return accountNumber;
}
public float getBalance() {
return balance;
}
public void setOwnerName(String newName) {
ownerName = newName;
}
public String toString() {
return ("Account#" + new java.text.DecimalFormat("000000").format(accountNumber) + " with balance"
+ new java.text.DecimalFormat("$0.00").format(balance));
}
public float deposit(float anAmount) {
balance += anAmount;
return (balance);
}
public float withdraw(float anAmount) {
balance -= anAmount;
return (balance);
}
public static BankAccount2 example1() {
BankAccount2 ba = new BankAccount2();
ba.setOwnerName(("LiHong"));
ba.deposit(1000);
return ba;
}
public static BankAccount2 example2() {
BankAccount2 ba = new BankAccount2();
ba.setOwnerName("ZhaoWei");
ba.deposit(1000);
ba.deposit(2000);
return ba;
}
public static BankAccount2 emptyAccountExample() {
BankAccount2 ba = new BankAccount2();
ba.setOwnerName("HeLi");
return ba;
}
}
測試程式AccountTester2.java
public class AccountTester2 {
public static void main(String[] args) {
BankAccount2 bobsAccount, marysAccount, biffsAccount;
bobsAccount = BankAccount2.example1();
marysAccount = BankAccount2.example1();
biffsAccount = BankAccount2.example2();
marysAccount.setOwnerName("Mary");
marysAccount.deposit(250);
System.out.println(bobsAccount);
System.out.println(marysAccount);
System.out.println(biffsAccount);
}
}
樣例輸出:
Account#000001 with balance$1000.00
Account#000002 with balance$1250.00
Account#000003 with balance$3000.00