個人理解,編程中所謂的 類¨ 與現實世界中對物體的分門別類中的類是同一個概念,只是在編程中將其借用進來。類代表有一系列共性和相同操作或動作的事物,在編程中即為抽象的數據類型。具體的每個個體(現實世界中)、實例變量(對于在編程中來說)就是對象。
類是現實世界某些對象的共同特征(屬性和操作)的表示,對象是類的實例。
類的屬性:就是類的靜態屬性的簡稱,指類內包含的各項數據,如變量或其他類的對象;
類的服務: 則被稱為成員函數或方法。
¨
Java中類的定義形式如下:
1
2
3
4
5
6
|
[修飾符] class 類名 [extends 父類] [implements 接口名] { 類成員變量聲明 類方法聲明 } |
我們再來仔細說說這中間的每一個部分:
在class關鍵字前,也即類的修飾符有大體分三種類型——訪問修飾符public公共類、final修飾符(最終類說明符)和abstract修飾符(抽象類說明符)
而其中,權限修飾符只能為public或默認(即為空,什么都沒有,表示定義為友好的),public表示該類可被任何地方使用和訪問(只要程序能找到該類位置),無論是在同一包內,還是在不同包。注意,這與C++中不同,C++中沒有還對類的訪問權限用修飾符來限制的,而是對類之間的繼承關系有訪問權限的說明,除此之外,它們倒是都對類的屬性和方法有訪問權限有限制。 默認的訪問權限(即定義為友好的),即是指只能被同一包內的類引用和訪問,而不能被其它包中的類訪問和引用,即使import進去。
后面還會提到:以類的屬性和方法缺省修飾符時,也表示為只能被同一包中的類引用和訪問。
Java中不允許多重繼承,這與C++中不同,也算是為了彌補這個不足,Java中引進了接口的概念。
上述類的定義中,類體中主要是類的具體內容,包括類的屬性和類的方法。類的屬性可以是簡單變量,也可以是某些類的實例,如果是類的實例,可以如下定義:
[修飾符] 類名 對象名=new類名(參數列表);
在聲明對象和復雜變量時,可以不在聲明時用創建,可以在以后的構造函數中創建。
類中定義的方法通常起到兩種作用:一是圍繞著類的屬性進行各種操作;二是與其他的類或對象進行數據交流、消息傳遞等操作。
Java中聲明方法的語法如下:
1
2
3
4
5
6
7
8
9
10
11
|
[修飾符] 返回值類型 方法名(參數列表) throws 例外名1,例外名2,… { 方法體: 局部變量聲明; 語句序列; } |
類的方法,又稱為成員函數,用來規定類屬性上的操作,實現類的內部功能的機制,同時也是類與外界進行交互的重要窗口。
Java程序員把注意力放在創建稱為類的用戶自定義類型(user-definedtype)上,類也稱為程序員定義的類型(programmer-definedtype),每個類都含有數據和一組操作數據的方法,類中的數據部分稱為實例變量。用戶定義類型(即類)的實例稱為對象。
對象是類的一個實例,類是同種對象的抽象,是創建對象的模板。在程序中創建—個對象將在內存中開辟一塊空間,其中包括該對象的屬性和方法。創建對象使用關鍵字運算符new。
構造函數(可以對比C++中,與C++幾乎相同)
創建自己的構造函數
•構造函數的名字和類的名字是相同的。當構造Employee類的對象時,此構造函數被啟動,實例字段賦初值,在Java中,定義和初始化是統一的——兩者缺一不可。
例如,用下面的代碼創建Employee類的一個實例時,
newEmployee (“James Bond”,100000,1950,1,1);
構造函數的特點有:
(1)構造函數和類具有相同的名字。
(2)一個類可以有多個構造函數。
(3)構造函數可以有0個、1個或多個參數。
(4)構造函數沒有返回值。
(5)構造函數總是和new運算符一起被調用。
構造函數的作用
(1)對象初始化
(2)引入更多的靈活度(變量賦值或更復雜的操作)
(3)Java中可以不定義構造函數
Java中可以不定義構造函數,此時系統會自動為該系統生成一個默認的構造函數。這個構造函數的名字與類名相同,它沒有任何形式參數,也不完成任何操作。
方法概述
Java程序是由一個個類定義組成的,類有兩個部分:屬性和方法。屬性描述類是什么,方法描述類做什么。任何對象都有獨立的內存存儲它的屬性。類的所有的對象共享存貯在內存的方法。
換言之:方法是類的主要組成部分。在一個類中,程序的作用體現在方法中。
方法即是JAVA創建一個有名字的子程序。一個主方法和若干個子方法構成。主方法調用其他方法,其他方法間也可互相調用,同一個方法可被一個或多個方法調用任意次。
在一個方法中定義另一個方法將產生語法錯誤。
(1)最好避免局部變量“屏蔽”實例變量,在一個類中不使用同名標識符就可以做到這一點;方法調用中參數用來傳遞數值、傳遞引用,同時方法還可以嵌套、遞歸調用。
(2)方法體中如果指定了非void的返回值類型,方法中就必須包含一條return語句保證任何情況下都有返回數值,return語句后面不能跟任何表達式;
Java程序的基本結構如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
引入Java類庫; 定義用戶類1 { 定義類1的若干變量或對象: 定義類1的方法1; 定義類1的方法2; … 定義類1的方法M1; } 定義用戶類2 { 定義類2的若干變量或對象: 定義類2的方法1; 定義類2的方法2; … 定義類2的方法M2 } |
Java推出了“訪問控制修飾符”的概念,允許庫創建者聲明哪些東西是客戶程序員可以使用的,哪些是不可使用的。
這種訪問控制的級別在“最大訪問”和“最小訪問”的范圍之間,分別包括:public,“默認”(無關鍵字),protected以及private。下面的列表說明訪問控制修飾符含義:
公共訪問控制符public
用于類:
Java中類的訪問控制符只有一個:public,即公共的。一個類被聲明為公共類,表明它可以被所有的其他類所訪問和引用,這里的訪問和引用是指這個類作為整體是可見和可使用的,程序的其他部分可以創建這個類的對象、訪問這個類內部可見的成員變量和調用它的可見的方法。
一個類作為整體對程序的其他部分可見,并不能代表類內的所有屬性和方法也同時對程序的其他部分可見,前者只是后者的必要條件,類的屬性和方法能否為所有其他類所訪問,還要看這些屬性和方法自己的訪問控制符。
用于類內屬性:
用public修飾的類內屬性稱為公共屬性,若這個類是公共類則它可以被所有的其他類訪問。
缺省訪問控制符
用于類
假如一個類沒有訪問控制符,說明它具有缺省的訪問控制特性。這種缺省的訪問控制權規定該類只能被同一個包中的類訪問和引用,而不可以被其他包中的類使用,這種訪問特性稱為包訪問性。通過聲明類的訪問控制符可以使整個程序結構清晰、嚴謹,減少可能產生類間干擾和錯誤。
用于類屬性
類內的屬性和方法如果沒有訪問控制符號來限定,也說明它們具有包訪問性。
3 私有訪問控制符private
用private修飾的屬性或方法只能被該類自身所訪問和修改,而不能被任何其他類,包括該類的子類,來獲取和引用
1). 私有數據
例如有三個實例字段,它們含有在Employee類的實例內部被操作的數據。
private string name;
private double salary;
private Date hireDay;
private(私有的)關鍵字用來確保可以訪問這些實例字段的只能是Employee類本身的方法。
2).私有方法
在實現類時,我們使所有的數據字段都是私有的,因為公開的數據是危險的。對于方法又是什么情況呢 ?雖然大多數方法是公開的,但是私有方法也經常使用。這些方法只能被同一個分離的方法。
總起來說,在下面的情況下可以選擇私有方法:
(1)與類的使用者無關的那些方法。
(2)如果類的實現改變了,不容易維護的那些方法。
保護訪問控制符protected
用protected修飾的成員變量可以被三種類所引用:該類自身、與它在同一個包中的其他類、在其他包中的該類的子類。使用protected修飾符的主要作用是允許其他包中的它的子類來訪問父類的特定屬性。
protected關鍵字為我們引入了一種名為“繼承”的概念,它以現有的類為基礎,并在其中加入新的成員,同時不會對現有的類產生影響——我們將這種現有的類稱為“基礎類”或者“基本類”(Base Class)。亦可改變那個類現有成員的行為。對于從一個現有類的繼承,我們說自己的新類“擴展”(extends)了那個現有的類
私有保護訪問控制符private protected
private和protected按順序連用構成一個完整的訪問控制符:私有保護訪問控制符。用privateprotected修飾的成員變量可以被兩種類訪問和引用,一種是該類本身,一種是該類的所有子類,不論這些子類是與該類在同一個包里,還是處于其他的包中。
相對于protected,privateprotected修飾符把同一包內的非子類排除在可訪問的范圍之外,使得成員變量更專有于具有明確繼承關系的類,而不是松散地組合在一起的包。
靜態修飾符
static稱為靜態修飾符,它可以修飾類中的屬性和方法。
使用static(靜態)關鍵字,可滿足兩方面的要求:
(1)一種情形是只想用一個存儲區域來保存一個特定的數據——無論要創建多少個對象,甚至根本不創建對象;被static修飾的屬性稱為靜態屬性,這類屬性一個最本質的特點是:它們是類的屬性,而不屬于任何一個類的具體對象。換句話說,對于該類的任何一個具體對象而言,靜態屬性是一個公共的存儲單元,任何一個類的對象訪問它時,取到的都是相同的數值,同樣任何一個類的對象去修改它時,也都是在對同一個內存單元做操作。
(2)另一種情形是我們需要一個特殊的方法,它沒有與這個類的任何對象關聯。也就是說,即使沒有創建對象,也需要一個能用類直接調用的方法。
static一項重要的用途就是幫助我們在不必創建對象的前提下調用那個方法。
靜態常量
靜態變量是很少見的。然而,靜態常量卻很普遍。例如,Math類中定義了一個靜態常量:
public class Math
1
2
3
4
5
6
7
8
9
|
{ …… public static final double PI= 3.1 . 4159265358979323846 ; …… } |
靜態方法
聲明一個方法為static至少有三重含義:
(1)使用這個方法時,應該使用類名做前綴,而不是某一個具體的對象名;
(2)非static的方法是屬于某個對象的方法,在這個對象創建時對象的方法在內存中擁有自己專用的代碼段;而static的方法是屬于整個類的,它在內存中的代碼段將隨著類的定義而分配和裝載,不被任何一個對象專有;
(3)由于static方法是屬于整個類的,所以它不能操縱和處理屬于某個對象的成員變量,而只能處理屬于整個類的成員變量。
5 main方法
main方法并不對任何對象施加操作。實際上,當程序開始執行時,還不存在任何對象。靜態方法被執行,并構造程序所需的對象。
提示 每個類都可以有一個main方法。這是對類進行單元測試的一個很方便技巧。
abstract是抽象修飾符,可以用來修飾類或方法。
抽象類
當一個類被聲明為abstract時,這個類被稱為是抽象類。所謂抽象類就是沒有具體實例對象的類。
針對這個問題,Java專門提供了一種機制,名為“抽象方法”。它屬于一種不完整的方法,只含有一個聲明,沒有方法主體。下面是抽象方法聲明時采用的語法:
abstract void X();
抽象方法
作為類方法修飾符,abstract則聲明了一種僅有方法頭,而沒有具體的方法體和操作實現的抽象方法。
可見,abstract方法只有方法頭的聲明,而用一個分號來代替方法體的定義:至于方法體的具體實現,那是由當前類的不同子類在它們各自的類定義中完成的。
需要特別注意的是,所有的抽象方法,都必須存在于抽象類之
中。
除了抽象方法,抽象類也可以有具體的數據和方法。
在Java編程語言中抽象方法是非常重要的概念。在接口里你會大量的用到它。
注意:這里要與接口進行對比、記憶,接口中的方法都屬于抽象方法,當然,接口中也有屬性,其具體性質將在后文詳述。
最終類、最終屬性、最終方法與終結器(C++中可沒有final最終修飾符)
final是最終修飾符,它可以修飾類、屬性和方法。另外終結器的關鍵字與final很相近,一并介紹
最終類
如果一個類被聲明為final,意味著它不能再派生出新的子類,不能作為父類被繼承。因此一個類不能既被聲明為abstract的,又被聲明為final的。
被定義成final的類,通常是一些有特殊作用的、用來完成標準功能的類,將一個類定義為final則可以將它的內容、屬性和功能固定下來,與它的類名形成穩定的映射關系,從而保證引用這個類時所實現的功能是準確無誤的
最終屬性
許多程序設計語言都有自己的辦法告訴編譯器某個數據是“常數”。常數主要應用于下述兩個方面:
(1)編譯期常數,它永遠不會改變;
(2)在運行期初始化的一個值,我們不希望它發生變化。
可以把一個實例字段定義為final(不能改變的)。在對象被構造時,這種字段必須被初始化。即,必須保證在每一個構造函數結束之前其值已被設定。以后字段的值不能改變
最終方法
之所以要使用final方法,可能是出于對兩方面理由的考慮。
第一個是為方法“上鎖”,防止任何繼承類改變它的本來含義。設計程序時,若希望一個方法的行為在繼承期間保持不變,而且不可被覆蓋或改寫,就可以采取這種做法。
采用final方法的第二個理由是程序執行的效率
終結器
終結器的作用是回收對象時執行的方法。類似與構造函數是創建對象時執行的方法一樣。
例
1
2
3
4
|
protected voidfinalize(){ System.out.println(“ “); } |
其它修飾符
volatile
如果一個屬性被volatile修飾,說明這個屬性可以同時被幾個線程所控制和修改。
synchronized
主要用于線程同步
native
表示該方法不是用java語言寫成的(是用C,C++等語言寫的)
網上查到的一點資料:——內部類
簡單的說,內部類就是類中的類,舉個例子:
1
2
3
4
5
6
7
8
9
10
11
12
|
class A { private int i; private void m() { } class B { mm( int j) { i = j; m(); } } } |
這里,B就是A的內部類
內部類的特點就是,可以方便的訪問外部類里面的私有方法和屬性,比如,這里B里面可以直接訪問A里面的私有屬性i,和私有方法m()
面向對象編程最重要的特征就是封裝性(也可稱作抽象性)、繼承性和多態性,那么,作為面向對象編程語言,Java在這方面更是有其出色之處:
“繼承性是軟件復用的一種形式,對降低軟件復雜性行之有效。繼承性同時是面向對象程序設計語言的特點,采用對象但沒有繼承性的語言是基于對象的語言,但不是面向對象的語言,這是兩者的區別”
類之間的繼承關系是現實世界中遺傳關系的直接模擬,它表示類之間的內在聯系以及對屬性和操作的共享,即子類可以沿用父類(被繼承類)的某些特征。當然,子類也可以具有自己獨立的屬性和操作
繼承性是軟件復用的一種形式。新類由已存在的類生成,通過保留它們的屬性和行為,并且根據新類的要求對性能加以修改,添加新的屬性和行為。如果子類只從一個父類繼承,則稱為單繼承;如果子類從一個以上父類繼承,則稱為多繼承。注意Java不支持多重繼承,但它支持“接口”概念。接口使Java獲得了多重繼承的許多優點,摒棄了相應的缺點。注意:C++支持多繼承
繼承關系的定義:
1
2
|
[修飾符]class子類名 extends父類名,父類名2 |
父類名跟在extends
關鍵字后面,用來說明當前類是哪個已經存在類的子類,存在繼承關系。
定義 雇員類 Employee的兩個子類:
一般雇員類:CommonEmployee
主 管 類:ManagerEmployee
子類從父類繼承有兩個主要的方面:
(1)屬性的繼承。例如,公司是一個父類,一個公司有名稱、地址、經理、雇員等,這些都屬于結構方面。
(2)方法的繼承。一個父類定義了若干操作,如一個公司要有項目、利潤、任命經理、錄用職工等操作,子公司也將繼承這些行為s;mp
1
2
3
4
5
6
7
8
9
10
|
classCommonEmployeeextends Employee//子類 1 : { intm_ManagerNo ;//定義類屬性m _ManagerNo,代表雇員上司的編號 } classManagerEmployeeextends Employee //子類 2 : { intm_SecretaryNo; //定義類屬性m_SecretaryNo,代表秘書的編號 } |
屬性繼承與隱藏
盡管Employee類是一個父類,但是并不因為它是父類就意味著它有更多的功能。恰恰相反,子類比它們的父類具有更多的功能。因為子類是父類的擴展,增加了父類沒有的屬性和方法
(1)子類不能訪問父類的private成員,但子類可以訪問其父類的public,
(2)protected訪問是public和private訪問之間一個保護性的中間層次。
(3)由于被繼承的父類成員沒有在子類聲明中列出,但是這些成員確實存在于子類中。
在這里,要區分一下繼承、覆蓋與重載這幾個易混淆的概念:
只有方法這一概念層面,這三個概念才易混淆:
方法繼承
對于子類對象,可以使用父類中的方法。即使這些方法沒有明顯地在子類中定義,它們也自動地從父類中繼承過來了
方法覆蓋
方法的覆蓋是指:子類定義同名方法來覆蓋父類的方法,是多態技術的一個實現。當父類方法在子類中被覆蓋時,通常是子類版本調用父類版本,并做一些附加的工作。
有很多注意事項,這里,我主要提一下this與super,C++中有this(且與Java中中概念差不多),但是沒有super。
this表示的是當前對象本身,this代表當前對象的一個引用。可以理解為對象的另一個名字。利用this可以調用當前對象的方法和屬性。
如:this.getName()和getName()在類中是一樣的。
super表示的是當前對象的直接父類對象,是當前對象的父類對象的引用
方法重載
重載的定義:可以用相同的方法名但不同的參數表來定義方法(參數表中參數的數量、類型或次序有差異),這稱為方法重載。
•重載(overloading):當多個方法具有相同的名字而含有不同的參數時,便發生重載。編譯器必須挑選處調用哪個方法。它通過將在不同方法頭部中的參數類型和在特定的方法調用中使用值的類型進行比較,從而挑選出正確的方法。
多態性允許以統一的風格處理已存在的變量及相關的類,使增加系統中新功能變得容易。這里,貼一下網上找的資料,能更清楚地將繼承中需特別注意的多態、繼承中的問題弄清:
Java的多態性
面向對象編程有三個特征,即封裝、繼承和多態。
封裝隱藏了類的內部實現機制,從而可以在不影響使用者的前提下改變類的內部結構,同時保護了數據。
繼承是為了重用父類代碼,同時為實現多態性作準備。那么什么是多態呢?
方法的重寫、重載與動態連接構成多態性。Java之所以引入多態的概念,原因之一是它在類的繼承問題上和C++不同,后者允許多繼承,這確實給其帶來的非常強大的功能,但是復雜的繼承關系也給C++開發者帶來了更大的麻煩,為了規避風險,Java只允許單繼承,派生類與基類間有IS-A的關系(即“貓”is a “動物”)。這樣做雖然保證了繼承關系的簡單明了,但是勢必在功能上有很大的限制,所以,Java引入了多態性的概念以彌補這點的不足,此外,抽象類和接口也是解決單繼承規定限制的重要手段。同時,多態也是面向對象編程的精髓所在。
要理解多態性,首先要知道什么是“向上轉型”。
我定義了一個子類Cat,它繼承了Animal類,那么后者就是前者是父類。我可以通過
1
|
Cat c = new Cat(); |
實例化一個Cat的對象,這個不難理解。但當我這樣定義時:
1
|
Animal a = new Cat(); |
這代表什么意思呢?
很簡單,它表示我定義了一個Animal類型的引用,指向新建的Cat類型的對象。由于Cat是繼承自它的父類Animal,所以Animal類型的引用是可以指向Cat類型的對象的。那么這樣做有什么意義呢?因為子類是對父類的一個改進和擴充,所以一般子類在功能上較父類更強大,屬性較父類更獨特,
定義一個父類類型的引用指向一個子類的對象既可以使用子類強大的功能,又可以抽取父類的共性。
所以,父類類型的引用可以調用父類中定義的所有屬性和方法,而對于子類中定義而父類中沒有的方法,它是無可奈何的;
同時,父類中的一個方法只有在在父類中定義而在子類中沒有重寫的情況下,才可以被父類類型的引用調用;
對于父類中定義的方法,如果子類中重寫了該方法,那么父類類型的引用將會調用子類中的這個方法,這就是動態連接。
看下面這段程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
class Father{ public void func1(){ func2(); } //這是父類中的func2()方法,因為下面的子類中重寫了該方法 //所以在父類類型的引用中調用時,這個方法將不再有效 //取而代之的是將調用子類中重寫的func2()方法 public void func2(){ System.out.println( "AAA" ); } } class Child extends Father{ //func1(int i)是對func1()方法的一個重載 //由于在父類中沒有定義這個方法,所以它不能被父類類型的引用調用 //所以在下面的main方法中child.func1(68)是不對的 public void func1( int i){ System.out.println( "BBB" ); } //func2()重寫了父類Father中的func2()方法 //如果父類類型的引用中調用了func2()方法,那么必然是子類中重寫的這個方法 public void func2(){ System.out.println( "CCC" ); } } public class PolymorphismTest { public static void main(String[] args) { Father child = new Child(); child.func1(); //打印結果將會是什么? } } |
上面的程序是個很典型的多態的例子。子類Child繼承了父類Father,并重載了父類的func1()方法,重寫了父類的func2()方法。重載后的func1(int i)和func1()不再是同一個方法,由于父類中沒有func1(int i),那么,父類類型的引用child就不能調用func1(int i)方法。而子類重寫了func2()方法,那么父類類型的引用child在調用該方法時將會調用子類中重寫的func2()。
那么該程序將會打印出什么樣的結果呢?
很顯然,應該是“CCC”。
對于多態,可以總結它為:
(1)使用父類類型的引用指向子類的對象(實際對象);
(2)該引用只能調用父類中定義的方法和變量;
(3)如果子類中重寫了父類中的一個方法,那么在調用這個方法的時候,將會調用子類中的這個方法;(動態連接、動態調用)
(4)變量不能被重寫(覆蓋),”重寫“的概念只針對方法,如果在子類中”重寫“了父類中的變量,那么在編譯時會報錯。
多態是通過:
(1) 接口 和 實現接口并覆蓋接口中同一方法的幾個不同的類體現的
(2) 父類 和 繼承父類并覆蓋父類中同一方法的幾個不同子類實現的.
1.基本概念
多態性:發送消息給某個對象,讓該對象自行決定響應何種行為。
通過將子類對象引用賦值給超類對象引用變量來實現動態方法調用。
ava 的這種機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。
(1)如果a是類A的一個引用,那么,a可以指向類A的一個實例,或者說指向類A的一個子類。
(2)如果a是接口A的一個引用,那么,a必須指向實現了接口A的一個類的實例。
Java多態性實現機制
SUN目前的JVM實現機制,類實例的引用就是指向一個句柄(handle)的指針,這個句柄是一對指針:
一個指針指向一張表格,實際上這個表格也有兩個指針(一個指針指向一個包含了對象的方法表,另外一個指向類對象,表明該對象所屬的類型);
另一個指針指向一塊從java堆中為分配出來內存空間。
總結
(1)通過將子類對象引用賦值給超類對象引用變量來實現動態方法調用。
1
2
3
|
DerivedC c2= new DerivedC(); BaseClass a1= c2; //BaseClass 基類,DerivedC是繼承自BaseClass的子類 a1.play(); //play()在BaseClass,DerivedC中均有定義,即子類覆寫了該方法 |
分析:
* 為什么子類的類型的對象實例可以覆給超類引用?
自動實現向上轉型。通過該語句,編譯器自動將子類實例向上移動,成為通用類型BaseClass;
* a.play()將執行子類還是父類定義的方法?
子類的。在運行時期,將根據a這個對象引用實際的類型來獲取對應的方法。所以才有多態性。一個基類的對象引用,被賦予不同的子類對象引用,執行該方法時,將表現出不同的行為。
在a1=c2的時候,仍然是存在兩個句柄,a1和c2,但是a1和c2擁有同一塊數據內存塊和不同的函數表。
(2)不能把父類對象引用賦給子類對象引用變量
1
2
|
BaseClass a2= new BaseClass(); DerivedC c1=a2; //出錯 |
在java里面,向上轉型是自動進行的,但是向下轉型卻不是,需要我們自己定義強制進行。
c1=(DerivedC)a2; 進行強制轉化,也就是向下轉型.
(3)記住一個很簡單又很復雜的規則,一個類型引用只能引用引用類型自身含有的方法和變量。
你可能說這個規則不對的,因為父類引用指向子類對象的時候,最后執行的是子類的方法的。
其實這并不矛盾,那是因為采用了后期綁定,動態運行的時候又根據型別去調用了子類的方法。而假若子類的這個方法在父類中并沒有定義,則會出錯。
例如,DerivedC類在繼承BaseClass中定義的函數外,還增加了幾個函數(例如 myFun())
分析:
當你使用父類引用指向子類的時候,其實jvm已經使用了編譯器產生的類型信息調整轉換了。
這里你可以這樣理解,相當于把不是父類中含有的函數從虛擬函數表中設置為不可見的。注意有可能虛擬函數表中有些函數地址由于在子類中已經被改寫了,所以對象虛擬函數表中虛擬函數項目地址已經被設置為子類中完成的方法體的地址了。
(4)Java與C++多態性的比較
jvm關于多態性支持解決方法是和c++中幾乎一樣的,
只是c++中編譯器很多是把類型信息和虛擬函數信息都放在一個虛擬函數表中,但是利用某種技術來區別。
Java把類型信息和函數信息分開放。Java中在繼承以后,子類會重新設置自己的虛擬函數表,這個虛擬函數表中的項目有由兩部分組成。從父類繼承的虛擬函數和子類自己的虛擬函數。
虛擬函數調用是經過虛擬函數表間接調用的,所以才得以實現多態的。
Java的所有函數,除了被聲明為final的,都是用后期綁定。
1個行為,不同的對象,他們具體體現出來的方式不一樣,
比如: 方法重載 overloading 以及 方法重寫(覆蓋)override
1
2
3
4
5
6
7
8
9
10
11
|
class Human{ void run(){輸出 人在跑} } class Man extends Human{ void run(){輸出 男人在跑} } 這個時候,同是跑,不同的對象,不一樣(這個是方法覆蓋的例子) class Test{ void out(String str){輸出 str} void out( int i){輸出 i} } |
這個例子是方法重載,方法名相同,參數表不同
ok,明白了這些還不夠,還用人在跑舉例
Human ahuman=new Man();
這樣我等于實例化了一個Man的對象,并聲明了一個Human的引用,讓它去指向Man這個對象
意思是說,把 Man這個對象當 Human看了.
比如去動物園,你看見了一個動物,不知道它是什么, "這是什么動物? " "這是大熊貓! "
這2句話,就是最好的證明,因為不知道它是大熊貓,但知道它的父類是動物,所以,
這個大熊貓對象,你把它當成其父類 動物看,這樣子合情合理.
這種方式下要注意 new Man();的確實例化了Man對象,所以 ahuman.run()這個方法 輸出的 是 "男人在跑 "
如果在子類 Man下你 寫了一些它獨有的方法 比如 eat(),而Human沒有這個方法,
在調用eat方法時,一定要注意 強制類型轉換 ((Man)ahuman).eat(),這樣才可以...
對接口來說,情況是類似的...
實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
package domatic; //定義超類superA class superA { int i = 100 ; void fun( int j) { j = i; System.out.println( "This is superA" ); } } // 定義superA的子類subB class subB extends superA { int m = 1 ; void fun( int aa) { System.out.println( "This is subB" ); } } // 定義superA的子類subC class subC extends superA { int n = 1 ; void fun( int cc) { System.out.println( "This is subC" ); } } class Test { public static void main(String[] args) { superA a = new superA(); subB b = new subB(); subC c = new subC(); a = b; a.fun( 100 ); a = c; a.fun( 200 ); } } |
/*
* 上述代碼中subB和subC是超類superA的子類,我們在類Test中聲明了3個引用變量a, b,
* c,通過將子類對象引用賦值給超類對象引用變量來實現動態方法調用。也許有人會問:
* "為什么(1)和(2)不輸出:This is superA"。
* java的這種機制遵循一個原則:當超類對象引用變量引用子類對象時,
* 被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,
* 但是這個被調用的方法必須是在超類中定義過的,
* 也就是說被子類覆蓋的方法。
* 所以,不要被上例中(1)和(2)所迷惑,雖然寫成a.fun(),但是由于(1)中的a被b賦值,
* 指向了子類subB的一個實例,因而(1)所調用的fun()實際上是子類subB的成員方法fun(),
* 它覆蓋了超類superA的成員方法fun();同樣(2)調用的是子類subC的成員方法fun()。
* 另外,如果子類繼承的超類是一個抽象類,雖然抽象類不能通過new操作符實例化,
* 但是可以創建抽象類的對象引用指向子類對象,以實現運行時多態性。具體的實現方法同上例。
* 不過,抽象類的子類必須覆蓋實現超類中的所有的抽象方法,
* 否則子類必須被abstract修飾符修飾,當然也就不能被實例化了
*/
以上大多數是以子類覆蓋父類的方法實現多態.下面是另一種實現多態的方法-----------重寫父類方法
JAVA里沒有多繼承,一個類之能有一個父類。而繼承的表現就是多態。一個父類可以有多個子類,而在子類里可以重寫父類的方法(例如方法print()),這樣每個子類里重寫的代碼不一樣,自然表現形式就不一樣。這樣用父類的變量去引用不同的子類,在調用這個相同的方法print()的時候得到的結果和表現形式就不一樣了,這就是多態,相同的消息(也就是調用相同的方法)會有不同的結果。舉例說明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
//父類 public class Father{ //父類有一個打孩子方法 public void hitChild(){ } } //子類1 public class Son1 extends Father{ //重寫父類打孩子方法 public void hitChild(){ System.out.println( "為什么打我?我做錯什么了!" ); } } //子類2 public class Son2 extends Father{ //重寫父類打孩子方法 public void hitChild(){ System.out.println( "我知道錯了,別打了!" ); } } //子類3 public class Son3 extends Father{ //重寫父類打孩子方法 public void hitChild(){ System.out.println( "我跑,你打不著!" ); } } //測試類 public class Test{ public static void main(String args[]){ Father father; father = new Son1(); father.hitChild(); father = new Son2(); father.hitChild(); father = new Son3(); father.hitChild(); } } |
都調用了相同的方法,出現了不同的結果!這就是多態的表現!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import java.io.*; class Super{ Super(){ System.out.println( "This is super class!" ); } void method(){ System.out.println( "Super's method" ); } } class Sub extends Super{ Sub(){ super (); System.out.println( "\n\t:and here is the child" ); } void method(){ System.out.println( "child's method" ); } } public class Super_Sub{ public static void main(String[] args){ Super sup= new Sub(); sup.method(); Sub child=(Sub) new Super(); //這里,實際分配的內存是Super的,但是卻用Child來指代它,這就是“向下轉型”(父類冒充子類,因為子類在UML中畫時是在下的嘛),必經強制類型轉換 child.method(); } } |
對于數據來說,繼承是否為正確的設計可以用一個簡單的規則來判斷。“is-a”規則表明子類的每一個對象都是一個超類的對象。例如,每一個經理是一個員工。然而,只有經理類是員工類的子類才是有意義的。很明顯,反過來就不行了——并不是每個員工都是經理。
還有一個明確敘述“is-a”規則的方法是替代原則。該原則規定無論何時,如果程序需要一個超類對象,都可以用一個子類對象來代替
動態綁定
理解調用一個對象方法的機制是非常重要的。下面具體介紹:X.f;
(1)編譯器檢查對象的聲明類型和方法名。
(2)接著,編譯器檢查方法調用中的參數類型。如果在所有的叫做f的方法中有一個其參數類型同調用提供的參數類型最匹配,那么該方法就會被選擇調用。這個過程稱作超載選擇。(靜態)
(3)當程序運行并且使用動態綁定來調用一個方法時,那么虛擬機必須調用同x所指向的對象的實際類型相匹配的方法版本。
……
如果類中沒有寫構造函數,那么系統會自動為該類提供一個默認構造函數,該構造函數將所有的實例字段初始化為默認值:
……
包用途:
Java允許把多個類收集在一起成為一組,稱作包(package)。包便于組織任務,以及使自己的任務和其他人提供的代碼庫相分離。
標準Java庫被分類成許多的包,其中包括java.1ang、java.util和java.net等等。標準Java包是分層次的。就像在硬盤上嵌套有各級子目錄一樣,可以通過層次嵌套組織包。所有的Java包都在Java和Javax包層次內
創建包
已經看到,已有的庫,比如JavaAPI中的類和接口,可以導入到Java程序中。
Java API中的每一個類和接口屬于一個特定的包。它包含一組相關聯的類和接口,實際是對類和接口進行組織的目錄結構。
例如,假定文件名是MyClass.java。它意味著在那個文件有一個、而且只能有一個public類。而且那個類的名字必須是MyClass(包括大小寫形式):
1
2
3
4
5
6
7
8
9
|
packagemypackage; publicclass MyClass { …… } |
創建可復用的類的步驟簡要說明如下:
(1)定義一個public類。如果類不是public,它只能被同一包中的其他類使用。
(2)選擇一個包名,并把package語句加到可復用的類的源代碼文件中。
(3)編譯這個類。這樣,它就被放到適當的包目錄結構中,以供編譯器和解譯器使用。
(4)把這個可復用的類導入到需要用它的程序中?,F在就可以使用它了。
注意 在Java語言中可以出現在類定義的括號外面的僅有兩個語句,它們是package和import。
包引用---每個類名前加上完整的包名
例如,給出一個指向此包中的類的快捷方式。一旦使用import(導入)了以后,就不再需要給出完整的包名。
可以引入一個特定的類,也可以引入整個包。import語句要放在源文件的頭部(但在所有package語句的下面)。例如,可以通過下面的語句引入在java.util包中的所有的類:
1
|
importjava.util.*; |
然后,就可以使用
1
|
Datetoday= new Date(); |
而不需要在前面加上包名。也可以引入包中某個特定的類:
1
|
importjava.util.Date; |
要把類放人一個包中,必須把此包的名字放在源文件頭部,并且放在對包中的類進行定義的代碼之前。例如,在文件Employee.java的開始部分如下:
1
2
3
4
5
6
7
8
9
|
packagecom.horstmann.corejava; publicclass Employee { …… } |
把包中的文件放入與此完整的包名相匹配的子目錄中。例如,在包com.horstmann.corejava中的所有的類文件都必須放在子目錄com/horstmann/core.java(Windows下的com\horstmann\corejava)下。這是最簡單的一種方法
類被存儲在文件系統的子目錄中。類的路徑必須與所在包名相匹配。
在前面的例子中,包目錄com/horstmann/corejava是程序目錄的一個子目錄。然而這樣安排很不靈活。一般,有多個程序需要訪問包文件。為了使包可以在多個程序間共享,需要做以下事情:
1)把類放在一個或多個特定的目錄中,比如/home/user/classdir。此目錄是包樹的基本目錄。如果加入了類com.horstmann.corejava.Employee,那么此類文件必須位于子目錄/home/user/classdir/com/horstmann/corejava下。
2)設置類路徑。類路徑是其子目錄包含類文件的所有基本目錄的集合。classpath
已經接觸過public和private訪問指示符。
被標記為Public的部件可以被任何類使用,而私有部件只能被定義它們的類使用。如果沒有指定public或private,那么部件(即類、方法或變量)可以被同一個包中的所有方法訪問。
Java API包
為了簡化面向對象的編程過程,Java系統事先設計并實現了一些體現了常用功能的標準類,如用于輸入/輸出的類,用于數學運算的類,用于圖形用戶界面設計的類,用于網絡處理的類等。這些系統標準類根據實現的功能不同,可以劃分成不同的集合,每個集合是一個包,合稱為類庫。可以引用這些包,也可以創建自己的包。
Java的類庫是系統提供的已實現的標準類的集合,是Java編程的API,它可以幫助開發者方便、快捷地開發Java程序
接口主要作用是可以幫助實現類似于類的多重繼承的功能。在Java中,出于簡化程序結構的考慮,不再支持類間的多重繼承而只支持單重繼承,即一個類至多只能有一個直接父類。然而在解決實際問題的過程中,僅僅依靠單重繼承在很多情況下都不能將問題的復雜性表述完整,需要其他的機制作為輔助。
接口聲明
Java中聲明接口的語法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
[public] interface 接口名 [extends 父接口名列表] { //接口體; //常量域聲明 [public] [static] [final] 域類型 域名=常量值; //抽象方法聲明 [public] [abstract] 返回值 方法名(參數列表) [throw異常列表]; } |
從上面的語法規定可以看出,定義接口與定義類非常相似,實際上完全可以把接口理解成為一種特殊的類,接口是由常量和抽象方法組成的特殊類
(1)接口中的屬性都是用final修飾的常量,
(2)接口中的方法都是用abstract修飾的抽象方法,在接口中只能給出這些抽象方法的方法名、返回值和參數列表,而不能定義方法體,即僅僅規定了一組信息交換、傳輸和處理的“接口”
接口的實現
一個類要實現某個或某幾個接口時,有如下的步驟和注意事項:
(1)在類的聲明部分,用implements關鍵字聲明該類將要實現哪些接口;
如下:
1
2
3
4
5
|
class類名implements接口{ } |
(2)如果實現某接口的類不是abstract的抽象類,則在類的定義部分必須實現指定接口的所有抽象方法,即為所有抽象方法定義方法體,而且方法頭部分應該與接口中的定義完全一致,即有完全相同的返回值和參數列表;
(3)如果實現某接口的類是abstract的抽象類,則它可以不實現該接口所有的方法。
(4)一個類在實現某接口的抽象方法時,必須使用完全相同的方法頭。
(5)接口的抽象方法,其訪問限制符都已指定是public,所以類在實現方法時,必須顯式地使用public修飾符。
小結:
多重繼承是指一個子類繼承多個父類。Java不支持多重繼承,但Java提供了接口。
子類不能訪問父類的private成員,但子類可以訪問其父類的public,protected和包訪問成員;要訪問父類的包訪問成員,子類一定要在父類的包內。
子類構造函數總是先調用(顯式的或隱式地)其父類的構造函數,以創建和初始化子類的父類成員。
子類的對象可以當作其父類的對象對待,反之則不行(即向上轉型)
protected訪問是public和private訪問之間一個保護性的中間層次。父類方法、子類方法和在同一個包內類的方法都能訪問父類的protected成員,但其他方法均不能訪問
一個子類對象引用可以隱式地轉換成一個父類對象引用。使用顯式的類型轉換,可以把父類引用轉換成子類引用。如果目標不是子類對象,將產生ClassCastException例外處理。