Java學習(二).類和對象
?
?
??? Java語言是一種面向對象的程序設計語言,類和對象是面向對象程序設計的基本概念。類是相似對象中共同屬性和方法的集合體。對象是類的實例。包是Java組織和管理類的一種方法。類的封裝性是面向對象程序設計的一個重要特點。
?
?
1、面向對象程序設計
?
1.面向對象程序設計的基本概念
???
Java語言是一種面向對象的程序設計語言。面向對象程序設計(Object Oriented Programming,簡稱OOP)是一種集問題分析方法、軟件設計方法和人類的思維方法于一體的、貫穿軟件系統分析、設計和實現整個過程的程序設計方法。面向對象程序設計方法的出發點和追求的基本目標,是使我們分析、設計和實現一個軟件系統的方法盡可能接近我們認識一個事物的方法。面向對象程序設計的基本思想是:對軟件系統要模擬的客觀實體以接近人類思維的方式進行自然分割,然后對客觀實體進行結構模擬和功能模擬,從而使設計出的軟件盡可能直接地描述客觀實體,從而構造出模塊化的、可重用的、維護方便的軟件。
??? 在現實世界中,客觀實體有兩大類:
??? (1)我們身邊存在的一切有形事物和抽象概念都是客觀實體。有形事物的例子如一個教師、一件衣服、一本書、一個飯店、一座樓、一個學校等;抽象概念的例子如學校校規、企業規定等;
???
(2)我們身邊發生的一切事件都是客觀實體,例如一場足球比賽、一次流感侵襲、一次到醫院的就診過程、一次到圖書館的借書過程,等等。
??? 不同的客觀實體具有各自不同的特征和功能。例如,飯店具有飯店的特征和功能,學校具有學校的特征和功能;又例如,就診過程具有就診過程的特征和功能,借書過程具有借書過程的特征和功能。
??? 現實世界中的一切客觀實體都具有如下特征:
??? ?有一個名字用來惟一地標識該客觀實體;
??? ?有一組屬性用來描述其特征;
??? ?有一組方法用來實現其功能。
???
例如,作為書店客觀實體,每個書店有不同的點名、負責人等屬性(特征),書店的入庫、出庫、圖書上架等是其基本的方法(功能)。
??? 面向對象程序設計方法下的軟件系統設計方法,首先是把問題中涉及的客觀實體分割出來,并設計成稱為類的可重復使用的模塊,然后定義出一個個實例化的類(稱為對象),再按照問題的要求,設計出用各個對象的操作完成的軟件系統。
?
2.類
???
在現實世界中,類這個術語是對一組相似客觀實體的抽象描述。例如,有A書店、B書店、C書店等。而書店類則是對書店這類客觀實體所應具有的共同屬性和方法的抽象描述。即書店類不是具體的描述,而是抽象的描述。
??? 在面向對象方法中,具體的客觀實體稱為對象,類是對具有相同屬性和相同方法的一組相似對象的抽象,或者說,類所包含的屬性和方法描述了一組對象的共同的屬性和方法。
??? 面向對象方法中軟件設計的主體是類。類是相同屬性和方法的封裝體,因此類具有封裝性;子類可以在繼承父類所有屬性和方法的基礎上,再增加自己特有的屬性和方法,因此類具有繼承性;在一個類層次中,定義為根類的對象可被賦值為其任何子類的對象,并根據子類對象的不同而調用不同的方法,因此類具有多態性。類的這種封裝性、多態性和繼承性,是面向對象程序設計的三個最重要的特點。
?
3.對象
???
類是具有相同屬性和方法的一組相似對象的抽象描述,但在現實世界中,抽象描述的類是并不具體存在的,例如,現實世界中只存在具體的A書店、B書店、C書店,并不存在抽象的書店。我們把按照類這個模板所定義的一個個具體的對象稱作類的實例,或稱作對象。
??? 首先,一個具體對象必須具有具體的屬性值,如A書店對象就必須具有如下屬性:書點名為A書店,負責人為張三等。其次,任何對象都具有相應類所規定的所有方法。
?
?
2、類
??? 面向對象方法中,軟件設計的最主要部分是設計類。類的設計可以劃分為類聲明和類主體設計兩部分。
?
1、類聲明
?
1.類聲明的格式
???
類聲明的格式如下:
??? [<修飾符>] class<類名>[extends<父類名>] [implements<接口名表>]
??? {
????? 類主體
??? }
???
其中,class是定義類的關鍵字,<類名>是所定義的類的名字,<父類名>是已經定義過的類名,<接口名表>是已經定義過的若干個接口名,當接口名多于一個時,用逗號分隔開。方括號表示該項是可選項。本章只討論基本的類設計方法,含有父類和接口的類將在下一章討論。
?
2.類的修飾符
??? 類聲明的<修飾符>分為訪問控制符和類型說明符兩部分,分別用來說明該類的訪問權限以及該類是否為抽象類或最終類。
??? (
1)訪問控制符public和默認
??? 當訪問控制符為public時,表示該類被定義為公共類。公共類表示該類能被任何類訪問。由于類都放于某個包中,包中的類互相能訪問,而不在一個包中的類互相不能直接訪問。如果要在一個包中訪問另一個包中的類,就必須用import語句導入所需要的類到該包中,但Java語言規定,被導入的類必須是用public修飾的類。
??? 當沒有訪問控制符public時,即是默認類(或稱缺省類)。默認類表示該類只能被同一個包中的類訪問,而不能被其他包中的類訪問。Java語言規定,一個Java文件中可以有很多類,但最多只能有一個公共類,其他都必須定義為默認類。
??? 例如: public? class? Teacher就聲明了一個公共類Teacher,該類可以通過import語句導入到其他包的類中,并能被其他所有的類訪問。又例如,class Student就聲明了一個默認類Student,該類只能被同一個包中的其他類訪問。
??? (
2)類型說明符abstract和final
??? 當類型說明符為abstract時,表示該類為抽象類,抽象類不能用來定義對象,抽象類通常設計成一些具有類似成員變量和方法的子類的父類。
??? 當類型說明符為final時,表示該類為最終類,最終類不能用來再派生子類。
??? 訪問控制符和類型說明符一起使用時,訪問控制符在前,類型說明符在后。例如,public abstract class Teacher就聲明了一個公共抽象類Teacher。
?
?
2、類主體設計
???
在上節討論面向對象程序設計時曾說過,類由成員變量和成員方法組成。因此,類主體設計包括類的成員變量設計和類的成員方法設計兩部分。由于Java語言中的所有方法必定屬于某個類,即方法一定是成員方法,所以成員方法可簡稱為方法。我們首先給出一個簡單的日期類設計,然后分別討論類成員變量和類方法的設計。
【例3.1】設計一個日期類。要求方法應包括設置日期、顯示日期和判斷是否是閏年。
類設計如下:
public class Date?????????????????? //類聲明
{
? //以下為類成員變量聲明
? private int year;?????????????????? //成員變量,表示年
? private int month;????????????????? //成員變量,表示月
? private int day;??????????????????? //成員變量,表示日
???
? //以下為類方法聲明和實現
? public void SetDate(int y,int m,int d)???? //設置日期值
??? {?????????????????????????????????
????? year = y;
????? month = m;
????? day = d;
??? }
???
? public void Print()??????????????????????? //輸出日期值
??? {
????? System.out.println("date is "+year+'-'+month+'-'+day);
??? }
???????
? public boolean IsLeapYear()??????????????? //判斷是否為閏年
??? {?????????????????????????????????
????? return (year%400==0) | (year%100!=0) & (year%4==0);
??? }
}
1.聲明成員變量
???
聲明一個成員變量就是聲明該成員變量的名字及其所屬的數據類型,同時指定其他一些附加特性。聲明成員變量的格式為:
??? [<修飾符>] [static] [final] [transient] <變量類型>? <變量名>;
??? 其中,<修飾符>有private、public和protected三種。當不加任何修飾符時,定義為默認修飾符。
???
private修飾符表示該成員變量只能被該類本身訪問,任何其他類都不能訪問該成員變量。
???
protected修飾符表示該成員變量除可以被該類本身和同一個包的類訪問外,還可以被它的子類(包括同一個包中的子類和不同包中的子類)訪問。
???
public修飾符表示該成員變量可以被所有類訪問。
??? 不加任何訪問權限限定的成員變量屬于默認訪問權限。默認訪問權限表示該成員變量只能被該類本身和同一個包的類訪問。
??? 上述修飾符實現了類中成員變量在一定范圍內的信息隱藏。這既符合程序設計中隱藏內部信息處理細節的原則,也有利于數據的安全性。
???
static指明該成員變量是一個類成員變量
???
final指明該成員變量是常量
???
transient指明該成員變量是臨時變量。
transient很少使用。
??? 類成員變量是一個類的所有對象共同擁有的成員變量。關于類成員變量的用途和使用方法我們將通過后面的例子說明。
??? 因此,例3.1中的成員變量year、month和day都是int類型的private成員變量,即這三個成員變量只能被該類本身訪問,任何其他類都不能訪問該成員變量。
?
2.聲明方法
???
聲明成員方法的格式為:
??? [<修飾符>] [static] <返回值類型>?<方法名>? ([<參數列表>])
??? {
? ??? <方法體>
??? }
???
其中,<修飾符>和成員變量的修飾符一樣,有private、public和protected三種,另外,還有默認。
??? 各個修飾符的含義也和成員變量修飾符的含義相同。static指明該方法是一個類方法。關于類方法的用途和使用方法我們將通過后面的例子說明。
??? 方法聲明中必須給出方法名和方法的返回值類型,如果沒有返回值,用關鍵字void標記。方法名后的一對圓括號是必須的,即使參數列表為空,也要加一對空括號。
??? 例如:
??? public void SetDate(int y,int m,int d)
??? 上述語句聲明了方法名為SetDate的public方法,其返回值為空,參數有三個,分別為y、m和d,這三個參數的數據類型均為int。
?
3.方法體
???
方法體是方法的具體實現。方法體的設計即是第2章討論的變量定義、賦值語句、if語句、for語句等根據方法體設計要求的綜合應用。例如,
??? public void SetDate(int y,int m,int d)???? //設置日期值
??? {?????????????????????????????????
????? year = y;??????? //給成員變量year賦值y
???? month = m;?????? //給成員變量month賦值m
??? day = d;???????? //給成員變量day賦值d
??? }
?
4.成員變量和變量
??? 初學者經常會混淆成員變量和變量,一個最簡單的區別方法是:定義在類中的都是成員變量,定義在方法內的都是變量。另外,還有定義在方法參數中的虛參變量,如例3.1 SetDate()中的y、m和 d。關于方法中定義的變量的例子見后面的設計舉例。
??? 成員變量和變量的類型既可以是基本數據類型(如int、long等),也可以是已定義的類。
?
3、構造方法
??? 在類的方法中,有一種特殊的方法專門用來進行對象的初始化,這個專門用來進行對象初始化的方法稱為構造方法。構造方法也稱作構造函數。一般來說,一個類中至少要有一個構造方法。
??? 構造方法在語法上等同于其他方法。因此構造方法的設計方法和前面說的其他方法的設計方法類同。但構造方法的名字必須與其類名完全相同,并且沒有返回值,甚至連表示空類型(void)的返回值都沒有。構造方法一般應定義為public。
??? 構造方法用來在對象創建時為對象的成員變量進行初始化賦值。其實現過程是:在創建對象時,將調用相應類中的構造方法為對象的成員變量進行初始化賦值。
??? 例如,我們可以為上述日期類設計一個如下的構造方法:
??? public Date(int y, int m, int d)????????? //構造方法
??? {
? ??? year = y;
? ??? month = m;
? ??? day = d;
??? }
?
?
3、對象
??? 類是一類相似對象的抽象描述,一個軟件系統是對具體問題的客觀事物進行模擬或描述的,因此需要具體的描述。對象是類的實例化,對象就是軟件系統中對具體問題的客觀事物進行的具體模擬或具體描述。
?
1、main方法
?
??? 類是作為許多相似對象的抽象描述設計的,如果要使用這個類時,就必須創建這個類的對象。那么,對象創建應該是在同一個類中呢?還是應該在另一個類中呢?答案是兩者都可以,但最好是在另一個類中。這樣沒有對象定義的純粹的類設計部分就可以單獨保存在一個文件中,就不會影響該類的重復使用。
??? Java語言規定,一個類對應一個.class文件,一個程序文件中可以包含一個或一個以上的類,但其中只允許一個類被定義成public類型。類中可以沒有main方法。但是要運行的類中必須有main方法。程序就是從main方法開始執行的。
??? 下面的例子是把對象創建在同一個類中的main方法中。
【例3.2】打印某個日期,并判斷該年是否是閏年。
public class Date???????????????????? //類聲明
{
? private int year;?????????????????? //成員變量,表示年
? private int month;????????????????? //成員變量,表示月
? private int day;??????????????????? //成員變量,表示日
? public Date(int y, int m, int d)????????? //構造方法
? {
??? year = y;
??? month = m;
??? day = d;
?? }
???
? //以下為其他類方法
? public void SetDate(int y, int m, int d)???? //設置日期值
? {
??? year = y;
??? month = m;
??? day = d;
? }
???
? public void Print()????????????????????????? //輸出日期值
? {
??? System.out.println("date is "+year+'-'+month+'-'+day);
? }
???????
? public boolean IsLeapYear()???????????????? //判斷是否閏年
? {?????????????????????????????????
??? return (year%400==0) | (year%100!=0) & (year%4==0);
? }
?
? public static void main(String args[])?? //main()方法
? {
??? Date a = new Date(2004, 8, 5) ;??????? //創建對象
??? a.Print();
??? if(a.IsLeapYear())
????? System.out.println(a.year + " 是閏年");
??? else
????? System.out.println(a.year + " 不是閏年");
? }
}
main方法必須放在類中,且格式必須為:public static void main(String args[])
2、對象的創建和初始化
?
??? 在例3.2中,語句Date a = new Date(2004, 8, 5);
??? 實現了定義對象a和為對象分配內存空間,并初始化對象a的成員變量數值為:
??? year = 2004; month = 8; day = 5;
??? 上述方法是把定義對象和創建對象這兩個步驟結合在了一起,并同時進行了對象的初始化賦值。這是最簡單、也是最經常使用的對象定義、創建和初始化方法。
??? 對象的定義和創建過程也可以分開進行,即首先定義對象,然后為對象分配內存空間,并可同時進行初始化賦值。
?
1.定義對象
???
定義對象的語句格式為:
??? <類名>?<對象名>;
??? 例如下面語句就定義了一個Date類的對象a:
??? Date a;
??? 對象和數組一樣,也是引用類型。即對象定義后,系統將給對象標識符分配一個內存單元,用于存放實際對象在內存中的存放位置。由于在對象定義時,對象在內存中的實際存放位置還沒有給出,所以,此時該對象名的值為空(null)。上述語句后對象a的當前狀態如下圖的(a)所示:
???
?
2.為對象分配內存空間和進行初始化賦值
???
和為數組分配內存空間一樣,為對象分配內存空間也使用new運算符。為對象分配內存空間的語句格式為:
??? <對象名>=? new <類名> ([<參數列表>]);
??? 其中,new運算符申請了對象所需的內存空間,new運算符返回所申請的內存空間的首地址。系統將根據<類名>和<參數列表>調用相應的構造方法為對象進行初始化賦值(即把參數值存入相應的內存單元中)。賦值語句把new運算符分配的連續地址的首地址賦給了對象名。正因為構造方法名和類名完全相同,所以這里的類名既用來作為new運算符的參數向系統申請對象所需的內存空間,又作為構造方法名為對象進行初始化賦值。例如:
??? a = new Date(2004, 8, 5) ;
??? 就先向系統申請了Date類對象所需的內存空間(其首地址由對象名a指示),又用參數2004、8和5調用了構造方法Date(2004, 8, 5),為對象a進行了初始化賦值。上述語句后對象a的當前狀態如上圖中的(b)所示。
??? 程序設計時最經常使用的方法,是在定義對象的同時為對象分配內存空間和進行初始化賦值。例如,
??? Date a = new Date(2004, 8, 5) ;
?
3.對象的使用
???
一旦定義并創建了對象(創建對象是指為對象分配了內存單元),就可以在程序中使用對象。
??? 對象的使用主要有三種情況,分別是:使用對象的成員變量或方法,對象間賦值和把對象作為方法的參數。
??? (1)
使用對象的成員變量或方法
??? 一旦定義并創建了對象,就可以使用對象的成員變量或方法。
??? 例如,例3.2的main()方法中a.year就使用了對象a的成員變量year。
??? 又例如,例3.2的main()方法中a.Print()就使用了對象a的成員方法Print()。
??? 另外,還可以修改對象的成員變量的數值,例如,如果在例3.2的main()方法中增加如下語句,就把對象a的成員變量year的數值修改為2005,
??? a.year = 2005;
??? (
2)對象間賦值
??? 對象可以像變量一樣賦值。例如,如果在例3.2的main()方法中增加如下語句,則對象b的值和對象a的值相同。
??? Date b;
??? b = a;
??? 但和變量賦值不一樣的是,對象賦值并沒有真正把一個對象的數值賦給另一個對象,而是讓另一個對象名(如對象名b)存儲的對象的首地址和這個對象名(如對象名a)存儲的對象的首地址相同。即對象的賦值是對象的首地址的賦值。例如,上述語句就把對象a的首地址值賦給了對象b,因此,對象b和對象a表示的是同一個對象。
??? 仔細分析上圖所示的對象的存儲結構,就可以理解對象間賦值的實現方法。
??? (
3)把對象作為方法的參數
??? 對象也可以像變量一樣,作為方法的參數使用。但系統實現兩者的方法不同,變量作為方法的實參時,系統把實參的數值復制給虛參;而對象作為方法的實參時,系統是把實參對象名(該對象必須已創建)指示的對象的首地址賦給了虛參對象名。其實現方法實際上和對象間賦值的實現方法相同。
?
4.垃圾對象的回收
???
從上面的討論可知,對象和變量在很多方面有些類同,例如,對象和變量都需要分配內存空間。但是,變量的內存空間是系統在變量定義時自動分配的,當變量超出作用范圍時,系統將自動回收該變量的內存空間。
??? 而對象的內存空間是在用戶需要時,用new運算符創建的。對象也有作用范圍,我們把超出作用范圍的對象(或稱不再被使用的對象)稱作垃圾對象。那么,誰來負責這些垃圾對象的回收呢?答案是,在Java中,收集和釋放內存是一個叫做自動垃圾回收線程的責任。線程的概念將在第10章討論,這里可以把自動垃圾回收線程理解為一個系統自己開啟、并與用戶程序并行運行的一個服務程序。自動垃圾回收線程在系統空閑時自動運行,這個線程監視用戶程序中所有對象的有效作用范圍,當某個對象超出其作用范圍時,該線程就對這樣的對象做上垃圾對象標識,并在適當的時候一次性回收這些垃圾對象。
??? 所以,用戶程序只需考慮為對象分配內存空間,不需考慮垃圾對象內存空間的回收。
?
5.實例成員變量與類成員變量
???
類有兩種不同類型的成員變量:實例成員變量與類成員變量。類成員變量也稱作靜態成員變量。
?
??? (1)
實例成員變量
??? 類定義中沒用關鍵字static修飾的成員變量就是實例成員變量,不同對象的實例成員變量其值不相同。
??? 例3.2中類Date的成員變量定義語句:
??? private int year;
??? private int month;
??? private int day;
??? 就定義了三個private類型的實例成員變量year、month和day。
??? 例如,若有如下對象定義:
??? Date a = new Date(2003, 1, 1) ;
??? Date b = new Date(2004, 5, 10) ;
??? 則對象a和對象b的實例成員變量數值就不同。如a.year的數值為2003,而b.year的數值為2004。
?
??? (2)
類成員變量
??? 用關鍵字static修飾的成員變量稱為類成員變量,一個類的所有對象共享該類的類成員變量。類成員變量可以用來保存和類相關的信息,或用來在一個類的對象間交流信息。
【例3.3】用類成員變量表示當前該類共有多少個對象被定義。
public class ObjectNumber???? //類聲明
{
? private static int number = 0;?? //類成員變量
???
? public ObjectNumber()???? //構造方法
? {?????????????????????????????????
??? number++;
? }
???
? public void Print()????? //對象個數輸出
? {
??? System.out.println(number);
? }
???????
? public static void main(String args[])
? {
??? ObjectNumber a = new ObjectNumber() ;?? //創建對象
??? System.out.print("當前對象個數為:");
??? a.Print();???????? //對象個數輸出
??? ObjectNumber b = new ObjectNumber() ;?? //創建對象?
??? System.out.print("當前對象個數為:");
??? b.Print();???????? //對象個數輸出
? }
}
輸出結果為:
當前對象個數為:1
當前對象個數為:2
?
6.實例方法與類方法
???
類有兩種不同類型的方法:實例方法與類方法。類方法也稱作靜態方法。
??? (
1)實例方法
??? 沒用關鍵字static修飾的方法就是實例方法。實例方法只能通過對象來調用。實例方法既可以訪問類成員變量,也可以訪問類變量。例3.2中類Date的SetDate()方法、 Print()方法和IsLeapYear()方法都是實例方法。這些實例方法體中都訪問了實例成員變量。
??? (2)
類方法
???
用關鍵字static修飾的方法稱為類方法。類方法通過類名來調用(也可以通過對象來調用)。類方法只能訪問類變量,不能訪問實例變量。類方法主要用來處理和整個類相關的數據。雖然類方法既可以用類名來調用,也可以用對象來調用,但用類名調用類方法程序的可讀性更好。
【例3.4】用類方法處理當前該類共有多少個對象被定義。
程序設計如下:
public class ObjectNumber2???? ??? //類聲明
{
? private static int number = 0;?? //類成員變量
???
? public ObjectNumber2()???? ????? //構造方法
? {
??? number++;
? }
???
? public static void Print()??? ?? //類方法
? {
??? System.out.println("當前對象個數為:" + number);
? }
???????
? public static void main(String args[])
? {
??? ObjectNumber2 a = new ObjectNumber2() ;? //創建對象
??? ObjectNumber2.Print();???? ?? //對象個數輸出
//? a.Print();//可以,但不提倡
??? ObjectNumber2 b = new ObjectNumber2() ;? //創建對象?
??? ObjectNumber2.Print();????? ?? //對象個數輸出
//? b.Print();//可以,但不提倡
??? }
}
輸出結果為:
當前對象個數為:1
當前對象個數為:2
?
7.方法的重寫
???
類的各種方法(包括構造方法和其他方法)都允許重寫(也稱做重載)。所謂方法重寫(overloading),是指一個方法名定義了多個方法實現。方法重寫時要求,不同的方法,其參數類型或參數個數要有所不同。若類的某個方法被重寫,該類的對象在訪問該方法時,以對象調用該方法的參數個數和參數類型與類的同名方法進行匹配,對象調用該方法的參數個數和參數類型與類定義中哪個方法的參數個數和參數類型完全一樣,則調用類中的哪個方法。
【例3.5】方法重寫示例。
public class Date2??????????????????? //類聲明
{
? private int year;?????????????????? //成員變量,表示年
? private int month;????????????????? //成員變量,表示月
? private int day;??????????????????? //成員變量,表示日
????
? public Date2(int y, int m, int d)?? //構造方法
? {
??? year = y;
??? month = m;
??? day ? = d;
? }
???
? public Date2()??????????? ????????? //構造方法
? {
??? year = 2004;
??? month = 8;
??? day? = 10;
? }
???
? public void Print()???????????????? //輸出日期值
? {
??? System.out.println("date is "+year+'-'+month+'-'+day);
? }
???
? public void Print(int y)??????????? //輸出日期值
? {
??? System.out.println("date is "+y+'-'+month+'-'+day);
? }
???????????
? public static void main(String args[])
? {
??? Date2 a = new Date2(2003,1,1) ;?? //創建對象
??? Date2 b = new Date2() ;?????????? //創建對象?
??? a.Print();
??? b.Print(2000);
? }
}
程序運行輸出:
date is 2003-1-1
date is 2000-8-10
??? 上述例子中,構造方法和Print()方法都重寫了兩個。第一個構造方法有三個參數,第二個構造方法沒有一個參數;第一個Print()方法沒有參數,第二個Print()方法有一個參數。在main()方法中,定義對象a時有三個參數,所以和第一個構造方法匹配,定義對象b時沒有參數,所以和第二個構造方法匹配;對象a調用方法Print()時沒有參數,所以和第一個Print()方法匹配,對象b調用方法Print()時有一個參數,所以和第二個Print()方法匹配。
??? 必須注意的是,方法重寫時必須做到:要么參數個數不同,要么參數類型不同。否則,系統無法識別與重寫的哪個方法匹配。但是,如果兩個重寫方法僅返回值的類型不同則不允許。
?
?
4、包
??? 面向對象程序設計的一個特點就是公共類資源可以重用。這樣,在設計一個軟件系統過程中設計的許多公共類(除包含有main方法的公共類外),就可以在以后的軟件系統設計中重復使用。但是,當應用軟件比較大時,就有許多Java文件,這些Java文件統統放在一個文件夾中,給以后的軟件資源重用帶來了許多不便。Java解決此問題的方法是包。
??? 包(package)是Java提供的文件(即公共類)的組織方式。一個包對應一個文件夾,一個包中可以包括許多類文件。包中還可以再有子包,稱為包等級。
??? Java語言可以把類文件存放在可以有包等級的不同的包中。這樣,在軟件系統設計時,就可以把相關的一組文件(即相關的一組公共類)存放在一個文件夾中,當文件夾太大時,還可以設計子文件夾按更細的分類方法存放相關文件,從而可以大大方便日后的軟件資源重用。Java語言規定,同一個包中的文件名必須惟一,不同包中的文件名可以相同。Java語言的包等級和Windows的文件組織方式完全相同,只是表示方法不同。
?
1、包的建立方法
?
1.定義文件所屬的包
???
簡單的包的定義語句格式為:
??? package <包名>;
??? 其中,package是關鍵字,<包名>是包的標識符。package語句指出了該語句所在文件所有的類屬于哪個包。
??? Java語言規定,如果一個 Java文件中有package語句,那么package語句必須寫在Java源程序的第一行。例如,下面的Java源程序MyClass.java中的類MyClass將屬于包MyPackage。
??? package MyPackage;
??? public class MyClass;
??? {
??? ……
??? }
?
2.創建包文件夾
???
程序員自定義的包(如前面例子的MyPackage包)必須通知系統其文件夾所在的路徑,這就需要用戶首先創建包的文件夾并設置包的路徑。創建包文件夾的方法是:
??? (
1)創建與包同名的文件夾
??? 例如,我們可以在D盤根目錄下創建一個與包同名的文件夾d:\MyPackage。注意,這里的包名MyPackage必須與package語句后的包名大小寫完全一致。
??? (
2)設置包的路徑
??? 用戶必須通過設置環境變量classpath,把用戶自定義包所在的路徑添加到環境變量classpath中。例如,作者設置的包MyPackage其路徑為d:\ ,所以要把d:\添加到環境變量classpath中。
??? 環境參數設置語句應改寫為:
??? set classpath=.;c:\jdk1.3.1\lib;d:\;
??? 環境參數設置說明:
??? ①分號(;)用來分隔開各項。因此,上述的設置共有三項。
??? ②c:\jdk1.3.1\lib是作者計算機上安裝的JDK1.3.1版本的系統包的路徑。
??? ③新添加的d:\是用戶自定義包文件夾的上一級路徑。
??? ④新添加的路徑d:\也可放在圓點(.)(表示當前工作路徑)前,則操作時只需把當前路徑下編譯成功的.class文件復制到自定義包文件夾中;如果路徑d:\放在圓點(.)后,則操作時需把當前路徑下編譯成功的.class文件移動到自定義包文件夾。
??? 當多個Java源程序文件中都有package語句,且package語句后的包名相同時,則說明這些類同屬于一個包。
??? 一個包還可以有子包,子包下還可以有子子包。在這種情況下,可以具體指明一個類所屬包的完整路徑。所以,完整的package語句格式為:
??? package? <包名>[.<子包名>[.<子子包名>…]];
??? 其中,在package語句中,圓點(.)起分隔作用;而在Windows的目錄中,圓點(.)和反斜杠(\)等義,即加一個圓點(.)就表示下一級目錄。
??? 當然,要把一個類放在某個子包或子子包中,前提條件是已經創建了與子包或子子包同名的目錄結構也相同的文件夾。
??? (
3)把編譯生成的.class文件移入包中
??? 用戶的源程序文件(即.java文件)通常存放在另外的文件夾中,.java文件編譯后產生的.class文件也存放在和.java文件相同的文件夾中。用戶在編譯.java文件成功后,需要把編譯成功的.class文件移入用戶自定義的包中。要保證包中有相應的.class文件,而當前工作目錄下沒有。
??? 例如,當上述的 MyClass.java文件編譯成功后,需要設計人員自己把MyClass.class文件移入到d:\ MyPackage文件夾中,否則系統會因找不到類而給出出錯信息。
?
2、包的使用方法
?
??? 包中存放的是編譯后的類文件(.class文件)。用戶可以在以后的程序中,通過import語句導入這些類,從而使用包中的這些類。
??? import語句的使用分兩種情況:
???
(1)導入某個包中的某個類;
???
(2)導入某個包中的全部類。
??? 這兩種情況分別用如下兩種形式的import語句:
??? import? MyPackage.MyClass; //導入包MyPackage中的MyClass類
??? import? MyPackage.*;? ???? //導入包MyPackage中的全部類,但不包含其子包
???
要說明的是,Java中的包是按類似Windows文件的形式組織的,Windows文件用反斜杠(\)表示一條路徑下的子路徑,而Java用圓點(.)表示一個包的子包。
【例3.6】設計一個日期類及其測試程序。
要求:把日期類放在包MyPackage中,以便于以后重復使用。
程序設計如下:
//Date1.java文件
package MyPackage;?????? ????????????? //定義類所屬的包
public class Date1???????????????????? //類聲明
{
? public int year,month,day;?????????? //成員變量,表示年、月、日
? public Date1(int y, int m, int d)??? //構造方法
? {
??? year = y;
??? month = m;
??? day = d;
? }
? public void print()????????????????? //輸出日期值
? {
??? System.out.println("日期是:" + year + '-' + month +? '-'+day);
? }
}
?
// UseDate1.java文件
import MyPackage.Date1;???? ?????????? //導入MyPackage中的Date1類
public class UseDate1
{
? public static void main(String args[])
? {
??? Date1 a = new Date1(2004,5,10) ;?? //創建對象
??? a.print();
? }
}
程序運行結果:
日期是:2004-5-10
程序設計說明:因UseDate1.java文件和Date1.java文件不在一個包中,所以,UseDate1.java文件要用import語句導入文件中使用的類。
總結編寫、運行上述帶有自定義包Java程序的操作步驟如下:
(1)創建文件夾。如在本地計算機的d盤創建文件夾MyPackage(d:\MyPackage)
(2)在環境變量中添加自定義包的路徑。如在autoexec.bat文件的classpath參數中添加d:\(注意:若在Windows98下,則設置完成后要運行一下該批處理文件)
(3)編譯包中類的.java文件。如在DOS下執行命令:javac Date1.java
(4)把編譯成功的.class文件移入包中。如把當前工作路徑下的Date1.class文件移動到文件夾d:\MyPackage中
(5)編譯導入包的.java文件。如在DOS下執行命令:javac UseDate.java
(6)運行導入包的.class文件。如在DOS下執行命令:java UseDate
?
?
3、包的訪問權限
?
??? 關于包中類的訪問權限已在3.2.1節討論,關于包中類的成員變量和方法的訪問權限已在3.2.2節討論。本節分相同包中的類和不同包中的類兩種情況舉例說明。
?
1.相同包中的類和類的成員的訪問權限
【例3.7】相同包中的訪問權限舉例。
程序設計如下:
//文件B1.java
package MyPackage;???? ??? //文件中定義的兩個類都在同一個包
class C1?????????????????? //C1聲明為缺省類
{
? int number;??????? ????? //默認成員變量number
? protected int age;?????? //protected成員變量age
???
? C1(int n, int a)????? ?? //構造方法
? {
??? number = n;
??? age = a;
? }
? public void output()???? // C1類的public方法
? {
??? System.out.println("number = " + number + "\n" + "age = " + age);
? }
}
?
public class B1??????????? //B1聲明為public類??????????
{
? public void output()???? //B1類的方法output()
? {
??? C1 s1 = new C1(0,0);? //B1類可以訪問同一個包中的默認類C1
??? s1.number = 1;?? ????? //同一包的對象可以訪問默認類的默認成員變量
??? s1.age = 25;???? ???? //同一包的對象可以訪問默認類的protected成員變量
??? s1.output();?????? ? ? //同一包的對象可以訪問默認類的public方法
? }
}
?
//文件D1.java
//類D1在當前工作路徑下
import MyPackage.B1;???? ? //導入MyPackage包中的B1類
public class D1
{
?public static void main(String args[])
?{
??? B1 t1 = new B1();
??? t1.output();
? }
}
程序的運行結果為:
number = 1
age = 25
程序說明:D1類中只能定義B1類的對象,不能定義C1類的對象(因C1類定義為默認類);但B1類中可以定義C1類的對象(因兩個類在同一個包中)。
?
2.不同包中的類和類的成員的訪問權限
?
【例3.8】不同包中的訪問權限舉例。
要求:把例3.7中的C1類和B1類分別放在兩個不同的包中。
程序設計如下:
//文件C2.java
package MyPackage.MyPackage1;
public class C2
{
? public int number;
? public int age;??
??
? public C2(int n, int a)
? {
??? number = n;
??? age = a;
? }
? public void output()
? {
??? System.out.println("number = " + number + "\n" + "age = " + age);
? }
}
?
//文件B2.java
package MyPackage;
import MyPackage.MyPackage1.C2;
public class B2?????????????
{
?public void output()
? {
??? C2 s1 = new C2(0,0);
??? s1.number = 1;?
??? s1.age = 25;???
??? s1.output();????
? }
}
?
//文件D2.java
import MyPackage.B2;
public class D2???????????
{
?public static void main(String args[])
?{
??? B2 t1 = new B2();
??? t1.output();
? }
}
程序的運行結果和例3.7的相同。
程序設計說明:
??? (1)把例3.7程序稍做改變,把C2類放在MyPackage.MyPackage1包中(當然,要建立相應的文件夾),把B2類放在MyPackage包中。當然,B2.java文件中要用import語句導入C2類。此時,由于B2類和C2類不在同一個包中,而C2類定義為默認類,所以編譯時語句:
??? C2 s1 = new C2(0,0);將出錯
??? 把C2類定義為public類后,則不會出現上述錯誤。但是,由于B2類和C2類不在同一個包中,所以編譯時語句:
??? s1.number = 1;
??? s1.age = 25;將出錯
??? 這是由于C2類的age成員變量定義為protected,number定義為默認,而修飾為protected和默認的成員變量不允許其他包中的C2類的對象調用;當把C2類的age和numbe成員變量的修飾符改為public,編譯成功。
???
(2)如果把C2類放在MyPackage包中,把B2類放在MyPackage.MyPackage1包中,則編譯時會出錯。這是由于JDK規定:在一個樹型結構的包中,上層包可以導入下層包,而下層包不可以導入上層包。在下層包的類中要使用上層包的類時,要在類前面加上包名。
?
4、系統定義的包
?
??? Java語言提供了許多方便用戶程序設計的基礎類,這些系統定義的類以包的形式保存在系統包文件夾中。如果要使用這些包中的類,必須在源程序中用import語句導入。其導入方法和前面介紹的導入自定義包方法類同。例如,要進行圖形用戶界面設計,需要導入系統包java.awt中的所有類,所以要在源程序中使用如下導入語句:
??? import? java.awt.*;???? //導入java.awt包中的所有類
??? 又例如,在進行圖形用戶界面設計時,還需要進行事件處理,因此需要導入圖形用戶界面包java.awt中的所有類和事件處理包java.awt.event中的所有類,所以要在源程序中使用如下導入語句:
??? import java.awt.*;???? //導入java.awt包中的所有類
??? import java.awt.event.*;?? //導入java.awt.event包中的所有類
??? 讀者也許會想,從Java語言包的組織形式看,顯然,java.awt.event包是java.awt包的子包,那么,僅有第一條導入語句似乎就可以了,第二條導入語句似乎沒有必要。
??? 讀者需要注意:第一條導入語句只導入了java.awt包中的所有類,并沒有導入java.awt包的子包,因此,也就沒有導入子包中的類。
?
?
5、內部類
?
??? 一個類被嵌套定義于另一個類中,稱為內部類(Inner Classes)或內隱類。包含內部類的類稱為外部類。
??? 內部類中還有一種更特殊的形式——匿名類,匿名類和內部類的功能類似。這里我們只討論內部類,不討論匿名類。
??? 內部類與前面討論的非內部類的設計方法基本相同,但除外部類外的其他類無法訪問內部類。當一個類只在某個類中使用,并且不允許除外部類外的其他類訪問時,可考慮把該類設計成內部類。
?
【例3.9】設計一個人員類,要求自動生成人員的編號。
設計思想:由于要自動生成人員的編號,因此可設計一個static成員變量,當每生成一個對象時,該成員變量自動加1;由于這樣的處理只是作用于外部類,所以把該處理過程用內部類的方法來實現。
程序設計如下:
public class PeopleCount??? ??? //外部類PeopleCount
{
? private String Name;
? private int ID;?????????????? //外部類的私有成員變量
? private static int count = 0; //外部類的static私有成員變量???
? public class People?????????? //內部類People
? {
??? public People()???? ??????? //內部類的構造方法
??? {
????? count++;????? ??????????? //訪問外部類的成員變量
????? ID = count;???? ????????? //訪問外部類的成員變量
???? }
???? public void output()?? ??? //內部類的方法
???? {
?????? System.out.println(Name + "的ID為:" + ID);
???? }
?? }
?? public PeopleCount(String sn) //外部類的構造方法
?? {
???? Name = sn;
?? }
?
?? public void output()????????? //外部類的方法
?? {
???? People p = new People();??? //建立內部類對象p
???? p.output();???????????????? //通過p調用內部類的方法
?? }
?? public static void main (String args[])
?? {
???? PeopleCount p1 = new PeopleCount("張三");
???? p1.output();
???? PeopleCount p2 = new PeopleCount("李四");
???? p2.output();???????
?? }
}
程序的運行結果為:
張三的ID為:1
李四的ID為:2
??? 程序設計說明:
??? 在外部類PeopleCount內嵌套定義了一個內部類People,當定義了外部類對象p1和p2后,調用p1或p2的方法output時,該方法將首先定義一個內部類對象,內部類對象的構造方法將先把外部類的static成員變量count加1,然后把count的值賦給人員編號成員變量PeopleID,然后輸出PeopleID的值。
??? 外部類與內部類的訪問原則是:在外部類中,一般通過一個內部類的對象來訪問內部類的成員變量或方法;在內部類中,可以直接訪問外部類的所有成員變量和方法(包括靜態成員變量和方法、實例成員變量和方法及私有成員變量和方法)。
??? 內部類具有以下特性:
? 內部類作為外部類的成員。Java將內部類作為外部類的一個成員,因此內部類可以訪問外部類的私有成員變量或方法。
? 內部類的類名只能用在外部類和內部類自身中。當外部類引用內部類時,必須給出完整的名稱,且內部類的類名不能與外部類的類名相同。
??? 在實際的Java程序設計中,內部類主要用來實現接口。
?
?
6、類的封裝性
?
??? 面向對象程序設計語言的一個重要特性是其封裝性,Java語言是按類劃分程序模塊的,Java語言很好地實現了類的封裝性。
??? 保證大型軟件設計正確性和高效性的一個重要原則是模塊化軟件設計。這需要設計許多可重復使用的模塊,然后在需要使用這些模塊的地方調用這些模塊。但是,如何劃分好模塊的界限,以保證模塊的正確性是非常重要的問題。因為如果某人隨意修改了已經被其他人使用的模塊,必將使程序出錯,并且這樣的錯誤很難發現和修改。
??? 在面向對象程序設計中,保證模塊正確性的基本方法是類的封裝性。類的封裝性是指類把成員變量和方法封裝為一個整體,這就劃分了模塊的界限。
??? 保證模塊正確性的措施則是由信息的隱藏性來實現的。類包括成員變量和方法兩部分。那些允許其他包程序訪問和修改的成員變量可以定義為public類型。那些只允許同在一個包中的其他類以及該類的子類訪問和修改的成員變量可以定義為protected類型。那些不允許其他類(內部類除外)訪問和修改的成員變量可以定義為private類型。我們說,private類型和protected類型的成員變量有效地隱藏了類的不能被隨意修改的成員變量信息,從而保證了共享的類模塊的正確性。類的封裝性和信息的隱藏性是雙胞胎,兩者是結合在一起的。
??? 同樣,那些允許其他包程序訪問的方法可以定義為public類型。那些只允許同在一個包中的其他類以及該類的子類訪問的方法可以定義為protected類型。那些不允許其他類(內部類除外)訪問的方法可以定義為private類型。類方法的不同類型定義,給調用者提供了權限明了的調用接口。
??? 和別的面向對象程序設計語言(如C++語言)相比,Java語言增加了包的概念。這樣,同一個包中類之間的信息傳遞就比較方便。
?
7、設計舉例
?
??? 本節給出一個較為復雜的程序設計舉例。
【例3.10】設計一個包括矩陣加和矩陣減運算的矩陣類,并設計一個測試程序完成簡單的測試。為簡化設計代碼,矩陣的元素值在構造方法中用隨機函數隨機給出。
程序設計如下:
//MyMatrix.java文件
public class MyMatrix????? //矩陣類MyMatrix
{
private int[][] table;??? //矩陣元素表
?? private int height;???? //矩陣的行
?? private int width;????? //矩陣的列
?
?? private void init(int m,int n)? //元素隨機賦值方法
?? {
????? table=new int[m][n];?? //分配矩陣元素數組內存空間
????? for(int i=0; i<m; i++)
??????? for(int j=0; j<n; j++)
??????? {
?????????? table[i][j]=(int)(Math.random() * 100); //元素隨機賦值
??????? }
? }
?
?? public MyMatrix(int n)??? //構造方法,構造方陣
?? {
?????? height = n;?????
?????? width = n;
?????? this.init(height,width);? //調用元素隨機賦值方法
?? }
?
?? public MyMatrix(int m,int n)?? //構造方法,構造m行n列矩陣
?? {
????? height=m;
????? width=n;
????? this.init(height,width);?? //調用元素隨機賦值方法
?? }
?
?? public int getHeight()??? //返回矩陣的行數方法
?? {
????? return height;
?? }
?
?? public int getWidth()??? //返回矩陣的列數方法
?? {
????? return width;
?? }
?
?? public int[][] getTable()?? //返回矩陣方法
?? {
????? return table;
?? }
?
?? public MyMatrix add(MyMatrix b)? //矩陣加方法
??? {?????
if(this.getHeight()!=b.getHeight()&&
this.getWidth()!=b.getWidth())
????? {
??????? System.out.println("the two matrix don't macth");
??????? return null;
????? }
????? MyMatrix result=new MyMatrix(b.getHeight(),b.getWidth());
????? for(int i=0;i<b.getHeight();i++)
??????? for(int j=0;j<b.getWidth();j++)
??????? {
?????????? result.table[i][j]=this.table[i][j]+b.table[i][j];
??????? }
????? return result;
?? }
?
?? public MyMatrix subtract(MyMatrix b)??? //矩陣減方法
?? {
????? if(this.getHeight()!=b.getHeight()&&
this.getWidth()!=b.getWidth())
????? {
??????? System.out.println("the two matrix don't macth");
??????? return null;
????? }
?
????? MyMatrix result=new MyMatrix(b.getHeight(),b.getWidth());
????? for(int i=0;i<b.getHeight();i++)
??????? for(int j=0;j<b.getWidth();j++)
??????? {
?????????? result.table[i][j]=this.table[i][j]-b.table[i][j];
??????? }
????? return result;
?? }
}
?
// TestMyMatrix.java文件
public class TestMyMatrix??????? //測試類
{
?? public static void main(String[] args)
?? {
???? MyMatrix mm1=new MyMatrix(4,4);
???? MyMatrix mm2=new MyMatrix(4,4);
???? MyMatrix mm3=new MyMatrix(4,5);
???? MyMatrix mm4=new MyMatrix(4,5);
?
???? MyMatrix add_result=mm1.add(mm2);
???? int[][] add_table=add_result.getTable();
???? MyMatrix subtract_result=mm3.subtract(mm4);
???? int[][] subtract_table=subtract_result.getTable();
?
???? System.out.println("two matrix add result:");
???? for(int i=0;i<add_result.getHeight();i++)
???? {
?????? for(int j=0;j<add_result.getWidth();j++)
?????? {
????????? System.out.print(add_table[i][j]+"? ");
?????? }
?????? System.out.println();
???? }
?
???? System.out.println("two matrix subtract result:");
??? for(int i=0;i<subtract_result.getHeight();i++)
??? {
?????? for(int j=0;j<subtract_result.getWidth();j++)
?????? {
???????? System.out.print(subtract_table[i][j]+"? ");
?????? }
?????? System.out.println();
??? }
?? }
}
程序運行結果如下:
two matrix add result:
67? 94? 130? 78
21? 171? 78? 104
47? 100? 84? 111
125? 152? 98? 61
two matrix subtract result:
-15? 88? 37? -25? -21
56? -5? 32? 40? 41
-56? 31? -75? -21? -4
-17? -46? -18? 2? -28