940 likes | 1.11k Views
類別與物件. 內 容 大 綱. 類別 方法 物件 封裝、多型與繼承 修飾字 抽象類別 介面. 類別 (1/4). Java 語言是一個 完全 (total) 物件導向程式設計語言 (Object Oriented Programming Language, OOPL) ,在 Java 語言中完全以建立 物件 (object) 的方式來撰寫程式。物件是 Java 語言中最重要的觀念。
E N D
內 容 大 綱 • 類別 • 方法 • 物件 • 封裝、多型與繼承 • 修飾字 • 抽象類別 • 介面
類別(1/4) • Java語言是一個完全(total)物件導向程式設計語言(Object Oriented Programming Language, OOPL),在Java語言中完全以建立物件(object)的方式來撰寫程式。物件是Java語言中最重要的觀念。 • 物件是Java語言的基礎,而類別(class)則是Java語言中建構物件的藍圖。Java語言利用類別(class)來描述物件(object)的建構方式,而物件則是依類別的描述建構出來的一個實體個例(instance)。
類別(2/4) • Java語言中類別的定義方式如下:
類別(3/4) • 我們可以發現類別的定義是利用class關鍵字帶出,並於此關鍵字之後列出類別的識別名稱【註解】,然後就是以一組配對大括號帶出類別的定義區塊,我們可以發現類別的定義區塊中包含類別的建構方法(constructor)及類別的二種成員:資料成員(data member)與方法成員(method member)。 • 資料成員(data member)就是一些可以用來儲存資料的變數(variable),而方法成員(method member)就是一些可以被呼叫執行的方法(method),建構方法(constructor)是一種很特別的方法,此種方法在類別用來建立物件時被呼叫執行。
類別(4/4) • 類別中,不管是變數或是方法都需要加以宣告,變數的宣告已經在第三章中描述過了,以下我們說明方法(包括建構方法與一般方法)的宣告及其用法。
方法(1/11) • 一個方法的定義方式如下所示:
方法(2/11) • 一個方法可以被呼叫而執行,而方法執行之後可以使用return敘述傳回一個傳回值,此傳回值必須指定一個傳回型別,此傳回型別可以是8種基本型別(boolean、byte、char、double、float、int、long、short)中的任何一種。請注意,一個方法return敘述傳回值的傳回型別必須出現在方法定義中的方法識別名稱之前,而若return敘述沒有傳回任何傳回值,則此return敘述可以省略,而且方法的傳回型別必須指定為void(空的),以表示無傳回值。
方法(3/11) • 方法的識別名稱之後跟著一組配對小括號,在這組配對小括號中可以放入任何數目(包含0個)的參數,每個參數都需要分別指出其型別。Java語言中的參數型別在方法被呼叫時是很重要的,我們在稍後將會提到。 • 以下是兩個方法的範例:
方法(4/11) • 範例1 //一個傳回"參數1"累加到"參數2"之所有整數總和的方法範例 int 總和(int 參數1, int 參數2){ int 臨時變數=0; for (int i=參數1; i<=參數2; ++i) 臨時變數 += i; return 臨時變數; }
方法(5/11) • 範例2 //一個印出"參數1"到"參數2"之間所有整數的方法範例 void 印出(int 參數1, int 參數2) { for (int i=參數1; i<=參數2; ++i) System.out.println(i); return; //沒有傳回值的return敘述可以省略 }
方法(6/11) • 上列範例方法中,方法總和的return敘述傳回一個int型別的值(臨時變數為int型別),因此,方法總和被宣告為int型別;而方法印出的return敘述之後沒有任何傳回值(該return敘述可以被省略),因此,方法印出被宣告為屬於void。 • 方法的定義區塊中包含了區域變數宣告與一些敘述,請讀者注意,在方法定義區塊中所宣告的變數稱為區域變數(local variable),其使用範圍僅限於方法的定義區塊之中已,也就是說,區域變數在方法的定義區塊之外即無法存取。例如,上列方法範例中總和方法裏宣告的臨時變數就是一個典型的區域變數。
方法(7/11) • 類別的建構方法(constructer)是一種特別的方法,此方法在類別用以建立物件時會被呼叫執行。類別的建構方法與類別方法成員中的一般方法相當類似,但是仍有以下三點不同之處: • 類別建構方法的識別名稱與類別識別名稱相同。 • 建構方法因為不需要返回傳回值,因此建構方法不需要列出傳回型別(而不是列成void型別)。 • 建構方法的呼叫方式需要配合new運算子的使用,與一般的方法的呼叫方式不相同。
方法(8/11) • 範例6-1中所列的腳踏車類別.java程式列出一個腳踏車類別的定義,腳踏車類別是一個類別,它包含一個資料成員:速度,一個沒有參數的建構方法及四個方法成員:加速、減速、停車及顯示速度。
方法(9/11) • //檔名: 腳踏車類別.java • //說明:一個定義類別「腳踏車類別」的例子 • class 腳踏車類別 { • double 速度; • 腳踏車類別() {速度=20.0;} • 腳踏車類別(double 參數) {速度=參數;} • double 加速(){速度*=1.1; return 速度;} • double 減速(){速度*=0.9; return 速度;} • double 停車(){速度=0.0; return 速度;} • void 顯示速度(){System.out.println(速度);} • } //類別:腳踏車類別 定義區塊結束
方法(10/11) • 本程式僅作為定義類別之用,無執行結果。 • 腳踏車類別.java程式在編譯完之後所產生的位元組碼檔腳踏車類別.class,並沒有辦法執行,因為它沒有定義main這個會自動執行的方法,因此,若嘗試由Java直譯器直接執行腳踏車類別,則會出現以下的訊息:
方法(11/11) • 我們可將腳踏車類別.class視為只是包含腳踏車類別定義的位元組碼檔。它可以提供其他程式使用腳踏車類別的定義。
物件(1/3) • 如前所述,類別(class)是描述物件(object)的建構藍圖,而物件則是依類別的描述建立出來的一個實體個例(instance)。以下我們使用範例6-2來說明如何由類別的定義來建構物件。
物件(2/3) • //檔名:腳踏車測試.java • //說明:說明如何由腳踏車類別定義來建構物件 • public class 腳踏車測試 { • public static void main (String[] 參數){ • 腳踏車類別 腳踏車1,腳踏車2,腳踏車3; • 腳踏車1=new 腳踏車類別(); • 腳踏車2=new 腳踏車類別(30.0); • 腳踏車3=new 腳踏車類別(40.0); • 腳踏車1.顯示速度(); • 腳踏車2.顯示速度(); • System.out.println(腳踏車3.速度); • System.out.println(腳踏車1.加速());
物件(3/3) • System.out.println(腳踏車2.減速()); • System.out.println(腳踏車3.停車()); • } //方法:main() 定義區塊結束 • } //類別:腳踏車測試 定義區塊結束 程式執行結果
封裝、多型及繼承 • 物件導向程式設計具有三個特色 封裝、多型及繼承,以下我們分別介紹之。
封裝(encapsulation)(1/4) • 所謂封裝(encapsulation)就是將資料本身與資料的處理細節一起置放於物件中的作法。資料封裝使得物件導向程式設計具有資訊隱藏(information hiding)特性,我們可以讓程式設計者無法直接存取成員變數而強迫程式設計者使用成員方法來改變或讀取成員變數,這可以避免資料成員變數被有意或無意的取用及更改。 • 程式設計師只要管如何呼叫成員方法而不必管成員方法存取了哪些成員資料與成員方法如何設計。也就是說,物件內部的製作方式(成員方法存取了哪些成員資料與成員方法如何設計)與物件對外界的介面(如何呼叫成員方法)被區隔開來,程式設計師因而可以放心的使用成員方法,而不須了解物件內部的實作細節。
封裝(encapsulation)(2/4) • 以下的範例程式可以展示Java語言的資訊隱藏特性。
封裝(encapsulation)(4/4) • 執行結果 (編譯時即有錯誤訊息)
多型(polymorphism)(1/5) • 多型(polymorphism)指的就是一個方法可以有許多型式。例如,若我們於範例程式6-1中程式腳踏車類別.java的第7行之後加入7.1及7.2二行,而使程式成為:
多型(polymorphism)(2/5) • 在加入7.1及7.2二行之後,方法加速有了三種型式,這就是方法加速的多型。第一種型式的加速方法沒有參數,第二種型式的加速方法有一個double型別的參數,而第三種型式的加速方法有一個int型別的參數。至於加速方法在被呼叫時,到底要採取那一個型式呢?這就必須藉由呼叫方法時所傳入的參數的個數與型別來決定了。 • 提示
多型(polymorphism)(3/5) • 例如,腳踏車測試.java程式中的第12行為 此敘述中呼叫沒有傳入參數的方法加速(也就是第一種形式的速度方法呼叫)。方法腳踏車1.加速() 完成呼叫之後變數速度會增加百分之十。若我們將腳踏車測試.java程式中的第12行改為
多型(polymorphism)(4/5) 則第二種型式的加速方法會被呼叫,因為呼叫加速方法時傳入的參數 0.25是一個double型別的數值,此時0.25會代入參數增速比例中,因此加速(0.25)在完成執行後速度的值會增加百分之25。而若我們將第12行改為 則第三種型式的加速方法會被呼叫,因為呼叫加速方法時傳入的參數5是一個int型別的數值,此時5會代入參數增速速度中,因此加速(5)在完成執行後速度的值會增加5。
多型(polymorphism)(5/5) • 上述的加速方法擁有許多不同的型式,並藉由傳入參數的個數與型別來決定採用的型式,這就是多型(polymorphism)觀念。擁有不同的型式的方法我們稱為多重定義的(overloaded),例如,上述的加速方法擁有不同的型式,我們稱加速方法為多重定義的。
繼承(inheritance) (1/4) • 繼承(inheritance)的觀念指的是物件可自其他物件延續使用其成員變數及成員方法,物件導向程式設計的繼承觀念可方便資料及方法的重覆使用。以下我們以範例程式6-4來說明繼承的概念。
繼承(inheritance)(2/4) 範例程式(檔名:摩托車繼承腳踏車.java) • //檔名:摩托車繼承腳踏車測試.java • //說明:一個說明摩托車類別繼承腳踏車類別的例子 • class 摩托車類別 extends 腳踏車類別{ • private int 引擎容量=150; • void 顯示速度(){System.out.println("速度"+速度);} • void 顯示引擎容量(){ System.out.println("引擎容量"+引擎容量);} • } //類別:摩托車類別 定義區塊結束 • public class 摩托車繼承腳踏車測試 { • public static void main (String[] 參數){ • 摩托車類別 摩托車1,摩托車2,摩托車3;
繼承(inheritance)(3/4) • 摩托車1=new 摩托車類別(); • 摩托車2=new 摩托車類別(); • 摩托車3=new 摩托車類別(); • 摩托車1.加速(); • 摩托車2.減速(); • 摩托車3.停車(); • 摩托車1.顯示速度(); • 摩托車2.顯示速度(); • 摩托車3.顯示速度(); • 摩托車1.顯示引擎容量(); • 摩托車2.顯示引擎容量(); • 摩托車3.顯示引擎容量(); • } //方法:main() 定義區塊結束
繼承(inheritance)(4/4) • } //類別:摩托車繼承腳踏車測試 定義區塊結束 • 執行結果(命令視窗指令:java Run摩托車繼承腳踏車測試)
修飾字(modifier)(1/2) • Java語言的修飾字有static、public、protected、private、final、abstract、synchronized、volatile、transient及native等,修飾字可以用以描述類別、方法或變數的屬性,以下是一些修飾字使用的例子,畫底線的都是修飾字: publicstatic void main(String[] 參數) private double 變數; protected static final int 常數=128;
修飾字(modifier)(2/2) • 修飾字可以任意次序出現,這完全不會影響它們的作用,但是,大部分的Java程式會採用以下的次序: <存取限制修飾字> static abstract synchronized volatile final native • 其中,<存取限制修飾字>可以是public或是protected或是private。 • 以下所列的修飾字中,synchronized及volatile用於多執行緒(multithreading),而native則用於加入其他語言程式碼於Java程式中。因為篇幅的關係,本章對於這三個修飾字先不加以說明。以下我們介紹其他修飾字的用法,我們首先介紹static修飾字。
static修飾字(1/8) • 修飾字static可以用以描述靜態變數及構成靜態方法。而靜態變數又稱為類別變數,靜態方法又稱為類別方法。 • 如前所述,類別是產生物件的根據,而透過new關鍵字就可以呼叫類別中定義的建構方法(constructor)來產生物件。基本上,每個物件都會有自己的成員變數及成員方法,並且使用物件名稱·成員變數識別名稱或物件名稱·成員方法識別名稱()的方式來存取。我們不可以在物件尚未產生之時,就直接使用成員變數或成員方法,如此在編譯時就會有錯誤產生,如以下實例所示:
static修飾字(2/8) class 類別 { int 變數; void 方法() { } //方法()的定義區塊是空的,因為僅作為測試之用 public static void main(String[] 參數) { System.out.println(變數); 方法(); } //方法:main() 定義區塊結束 } //類別:類別 定義區塊結束 • 編譯錯誤產生在第5行及第6行,其訊息分別為 Can't make a static reference to nonstatic variable 變數 in class 類別 • 及 Can't make static reference to method void 方法() in class 類別
static修飾字(3/8) • 為解決上述的二個編譯錯誤,我們可以在類別相對的物件產生之後才存取類別中的變數及方法,如以下的程式碼: class 類別 { int 變數; void 方法() { } //方法()的定義區塊是空的,因為僅作為測試之用 public static void main(String[] 參數) { 類別 物件=new 類別(); System.out.println(物件.變數); 物件.方法(); } //方法:main() 定義區塊結束 } //類別:類別 定義區塊結束
static修飾字(4/8) • 我們也可以使用static修飾字來解決上述編譯錯誤的問題,其做法為在第2行的變數宣告及第3行的方法宣告開頭加上static修飾字,如下所示: class 類別 { static int 變數; static void 方法() { } //方法()的定義區塊是空的,因為僅作為測試之用 public static void main(String[] 參數) { System.out.println(變數); 方法(); } //方法:main() 定義區塊結束 } //類別:類別 定義區塊結束
static修飾字(5/8) • 一旦變數或方法加上static修飾字之後,他們就形成靜態變數(staticvariable)或靜態方法(static method),這種變數或方法不必等到物件產生就可以立即存取。 • 靜態變數或靜態方法由所有屬於此一類別的物件所共用,因此,他們又稱類別變數或類別方法,其存取或呼叫方式為類別名稱·成員名稱。當然,類別變數或方法在物件個例建立之後,仍然可以存取,其存取方式為物件名稱·成員名稱。 • 建議使用類別名稱·成員名稱的方式來存取類別變數或呼叫類別方法,因為這可突顯出類別變數或類別方法是由整個屬於此一類別的物件個例所共用的特點。
static修飾字(6/8) • 例如,JOptionPane.showInputDialog(…)就是一個以類別名稱·成員名稱的方式來呼叫JOptionPane類別的靜態方法showInputDialog的例子。 • 另一個例子是System.out.println(…)方法的呼叫,out是System類別中的一個靜態資料成員,此資料成員是一個物件,它所屬的類別中定義了一個靜態方法println,因此我們直接以 類別名稱·成員名稱.成員名稱(System.out.println)的方式來呼叫println方法。
static修飾字(7/8) • 靜態變數由所有屬於同一類別的物件所共用的特性還有另外一個用途,就是可以當作共用的計數器。例如, class 腳踏車類別 { static long 計數器=0; private long 腳踏車編號; 腳踏車類別(){ 計數器++; 腳踏車編號=計數器; } } //類別:腳踏車類別 定義區塊結束 • 在上列範例中腳踏車類別中的靜態變數計數器之初始值為0,靜態變數(類別變數)初始值的設定只執行一次,而且在所有物件個例產生前就已經執行完畢,因此,類別變數的初始值設定不會隨著每一個物件個例的產生而再度進行。
static修飾字(8/8) • 每次有一個屬於腳踏車類別的物件建立時,腳踏車類別的建構方法會被呼叫一次,此時所有物件共用的靜態變數計數器會遞增1而且這個值會被存入變數腳踏車編號中。請注意,變數計數器是所有物件共用而且是唯一的,因此可以用來記錄所有屬於腳踏車類別的物件的總數,而變數腳踏車編號不是唯一的,每個屬於腳踏車類別的物件都有屬於自己的腳踏車編號變數,因此可以用來儲存每一個腳踏車類別物件的個別編號。 提示
存取限制修飾字(1/8) • 以下我們介紹存取限制修飾字,這類修飾字有三個─public、private及protected,它們可用於修飾類別及類別成員(即變數與方法),以下我們先說明存取限制修飾字於類別成員(變數或方法)上之用法,其用法可分四類: • 加上public(公開的)修飾字:這表示類別成員(變數或方法)是公開的,這種類別成員可以被任何其他類別存取及繼承。 • 加上private(不公開的)修飾字:這表示類別成員(變數或方法)是不公開的,這種類別成員僅可在其所屬的類別定義區塊中被存取,而且不能被繼承。
存取限制修飾字(2/8) • 加上protected(保護的)修飾字:這表示在相同類別庫(package)裏的類別可以存取或繼承一個宣告為protected的成員,但是在不同類別庫裏的類別則不能存取而僅能繼承這些宣告為protected的成員。類別庫(package)是Java語言中很重要的觀念,可用以將許多相關的類別整理在一起,我們將在下一章中介紹類別庫的用法,目前,讀者只要將「在相同類別庫(package)」先視為「在相同檔案目錄(directory)」或「在相同檔案夾」即可。
存取限制修飾字(3/8) • 不加任何存取限制修飾字:這表示在相同類別庫(package)裏的類別可以繼承及存取一個不加任何存取限制修飾字的變數或方法。但是若在不同類別庫則不可繼承及存取這類變數或方法。此種不加存取限制修飾字的存取限制方式又稱package(類別庫)存取限制或friendly(友善)存取限制。
存取限制修飾字(4/8) • 我們舉以下的例子來看上列四種類別成員的存取限制: • class 類別1{ • static public int 變數1=1; • static private int 變數2=2; • static protect int 變數3=3; • static int 變數4=4; • } // • class 類別2 extends 類別1 { • public static void main(String[] 參數){ • 類別1 類別1物件=new 類別1(); • System.out.println(變數1); //測試繼承限制 • //System.out.println(變數2); //測試繼承限制 • System.out.println(變數3); //測試繼承限制 • System.out.println(變數4); //測試繼承限制 • System.out.println(類別1物件.變數1); //測試繼承限制 • //System.out.println(類別1物件.變數2); //測試繼承限制 • System.out.println(類別1物件.變數3); //測試繼承限制 • System.out.println(類別1物件.變數4); //測試繼承限制 • }
存取限制修飾字(5/8) • 若將第11行的註解標記取消,則在編譯時會出現 Undefined variable:變數2 的錯誤訊息,這是因為變數2在類別1中是宣告為private(不公開)的,因此,類別2根本沒有自類別1中繼承變數2。 • 另外,若將第15行的註解標記取消,則在編譯時會出現 Variable 變數2 in class 類別1 not aaccessible form class 類別2 的錯誤訊息,這是因為變數2在類別1中是宣告為private(不公開)的,除非是在類別1的定義區塊中,否則是無法存取變數2的。 • 變數1在類別1中宣告為public(公開的),因此可以由類別2繼承或存取。宣告為protected的成員,如變數3,在相同類別庫中可繼承亦可存取(但在不同類別庫中的類別則不可存取此變數);另外,不加宣告的成員,如變數4,可被相同類別庫中的類別繼承及存取。
存取限制修飾字(6/8) • 以上是上述四種存取情形的整理: