Java程式設計(2021春)——第二章筆記與思考
- 2021 年 7 月 11 日
- 筆記
- 學堂在線《Java程式設計(2021春)》
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