4、面向物件程式設計
4、面向物件程式設計
面向物件的三大特徵:
封裝 (Encapsulation)
繼承 (Inheritance)
多型 (Polymorphism)
面向過程與面向物件
面向過程(POP) 與 與 象 面向物件(OOP)
-
二者都是一種思想,面向物件是相對於面向過程而言的。
面向過程,強調的是功能行為,以函式為最小單位,考慮怎麼做。
面向物件,將功能封裝進物件,強調具備了功能的物件,以類/物件為最小單位,考慮誰來做。
-
面向物件更加強調運用人類在日常的思維邏輯中採用的思想方法與原則,如抽象、分類、繼承、聚合、多型等。
舉例:
面向物件思想:
- 程式設計師從面向過程的執行者
- 面向物件分析方法分析問題的思路和步驟:
根據問題需要,選擇問題所針對的現實世界中的實體。
從實體中尋找解決問題相關的屬性和功能,這些屬性和功能就形成了概念世界中的類。
把抽象的實體用計算機語言進行描述,形成計算機世界中類的定義。即藉助某種程式語言,把類構造成計算機能夠識別和處理的資料結構。
將類例項化成計算機世界中的物件。物件是計算機世界中解決問題的最終工具
類和物件
類(Class)和物件(Object)是面向物件的核心概念。
- 類是對一類事物的描述,是抽象的、概念上的定義
- 物件是實際存在的該類事物的每個個體,因而也稱為例項(instance)。
類的構成:
- Field屬性:類中的成員變數
- Method方法:類中的(成員)方法--函式
類的語法格式:
- 定義類(考慮修飾符、類名)
- 編寫類的屬性(考慮修飾符、屬性型別、屬性名、初始化值)
- 編寫類的方法(考慮修飾符、返回值型別、方法名、形參等)
類的訪問機制:
- 在一個類中的訪問機制: 類中的方法可以直接訪問類中的成員變數 。
(例外:static 方法訪問 非static, 編譯不通過 。 ) - 在不同類中的訪問機制: 先建立要訪問類的物件 , 再用物件訪問類中定義的成員
類的建立和使用
建立物件語法: 類名 物件名 = new 類名();
使用 物件名. 物件成員
匿名物件:
- 不定義物件的控制代碼,而直接呼叫這個物件的方法。這樣的物件叫做匿名物件。
如:new Person().shout();
- 使用情況
如果對一個物件只需要進行一次方法呼叫,那麼就可以使用匿名物件。
我們經常將匿名物件作為實參傳遞給一個方法呼叫。
記憶體解析:
類的成員(3+2)
屬性
語法格式:
變數分類:
成員變數與區域性變數的區別:
屬性的賦值順序:
① 預設初始化
② 顯式初始化
③ 構造器中初始化
④ 通過“物件.屬性“或“物件.方法”的方式賦值
方法
什麼是方法(method 、函式):
- 方法是類或物件行為特徵的抽象,用來完成某個功能操作。在某些語言中也稱為函式或程式。
- 將功能封裝為方法的目的是,可以實現程式碼重用,簡化程式碼
- Java裡的方法不能獨立存在,所有的方法必須定義在類裡。
方法的宣告格式:
注意:
- 方法被呼叫一次,就會執行一次
- 方法中只能呼叫方法或屬性,不可以在方法內部定義方法。
方法的過載
overloads
- 方法名相同
- 引數列表必須不同
- 返回值型別無關
方法的重寫
覆蓋/ 重置/ override/ overwrite
重寫的前提是 子類繼承父類的 非私有,非靜態(static)方法;
要求:
- 方法名相同,引數列表相同
- 返回值型別 小於等於 父類
- 訪問許可權 大於等於 父類
- 丟擲異常 小於等於 父類
過載與重寫對比 :
方法的引數傳遞
對於值的傳遞:
- 當形參是基本資料型別時,實參傳遞“資料值”副本,實參值不受影響
- 當形參時引用資料型別時,實參傳遞“地址值”副本,實參值受影響。(String不改變)
傳入物件的情況:
傳入物件時,若根據引用把堆裡的物件修改了,那麼物件真被修改了,不過不是被建立賦值給的那個引用修改的,是方法裡的一個複製的引用副本給修改的。
形參與實參:
- 形參:方法宣告時的引數
- 實參:方法呼叫時實際傳入的引數值
構造器(構造方法)
修飾符 類名(引數列表){ 初始化語句; }
根據引數不同,構造器可分為兩類:
- 隱式無參構造器(系統預設提供)
- 顯示定義一個或多個構造器(無參,有參)
構造器的特徵:
它具有與類相同的名稱
它不宣告返回值型別。(與宣告為void不同)
不能被static、final、synchronized、abstract、native修飾,不能有return;構造器的作用:
建立物件,給物件進行初始化
注意:
Java 語言中,每個類都至少有一個構造器
預設構造器的修飾符與所屬類的修飾符一致
一旦 顯式定義了 構造器, 則系統不再提供預設構造器
一個類可以建立多個 過載 的構造器
父類的構造器不可被子類繼承構造器的過載:
構造器一般用來建立物件的同時初始化物件
構造器過載使得物件的建立更加靈活,方便建立各種不同的物件。
程式碼塊(初始化塊)
程式碼塊的作用:用來初始化類,物件。
程式碼塊的內部可以有輸出語句;
可以定義多個程式碼塊,按照宣告的先後順序執行;
程式碼塊可以分為 靜態程式碼塊 與 非靜態程式碼塊:(靜態優先於非靜態程式碼塊執行)
靜態程式碼塊
static{ }
隨著類的載入而執行,且只執行一次。
用於初始化類的資訊非靜態程式碼塊
{ }
隨著物件的建立而執行,每建立一個物件就執行一次。
用於 在建立物件時,對物件的屬性等進行初始化。
成員變數的賦值順序:
- 宣告成員變數的 預設初始化
- 顯式初始化 / 程式碼塊賦值(同級別下按先後順序)
- 構造器初始化
- 有了物件後,
物件.屬性
或物件.方法
賦值。
內部類
Java中允許將一個類A宣告在另一個類B中, 則類A就是內部類,類B稱為外部類
內部類的分類:
**成員內部類 **(靜態,非靜態)
區域性內部類 (方法內,程式碼塊內,構造器內)成員內部類:
- 作為外部內的成員:
呼叫外部類的結構
可以被static修飾
可以被4中不同的許可權修飾- 作為一個類:
類內可以定義屬性,方法,構造器等
可以被final修飾,表示此類不可繼承(反之可以被繼承)
可以被abstract修飾關注三個問題:
如何例項化成員內部類的物件
//1.靜態成員內部類 Person.Dog dog = new Person.Dog(); //2.非靜態成員內部類 Person p = new Person(); Person.Dog dog = p.new Dog(); //通過外部類例項來 建立內部類物件
如何在成員內部類中區分呼叫外部類的結構
public void say(String name){ sout(name); //方法的形參 sout(this.name); //內部類的屬性 sout(Person.this.name); //外部類的屬性 }
開發中區域性內部類的使用
//返回一個實現了Comparable介面的 物件 public Comparable getComparable(){ //方式一: //建立一個實現了Comparable介面的類:區域性內部類 class MyComparable implements Comparable{ public int compareTo(Object o){return 0;} //實現介面方法 } //返回區域性內部類物件 return new MyComparable; //方式二: return new Comparable(){ public int compareTo(Object o){return 0;} } }
類的三大特徵
封裝和隱藏
封裝的作用和含義?
-
我們程式設計追求“高內聚,低耦合”
高內聚 :類的內部資料操作細節自己完成,不允許外部干涉;
低耦合 :僅對外暴露少量的方法用於使用。 -
隱藏物件內部的複雜性,只對外公開簡單的介面。便於外界呼叫,從而提高系統的可擴充套件性、可維護性。通俗的說,把該隱藏的隱藏起來,該暴露的暴露出來。這就是封裝性的計思想。
資訊的封裝和隱藏方式:
Java中通過將資料宣告為私有的private,再提供公共的public方法: getXxx() 和 setXxx() 實現對該屬性的操作,以實現下述目的:
- 隱藏一個類中不需要對外提供的實現細節;
- 使用者只能 通過事先指定好的 方法來訪問資料 可以方便地加入控制邏輯,限制對屬性的不合理操作;
- 便於修改,增強程式碼的可維護性;
四種訪問許可權修飾符:
Java許可權修飾符public、protected、(預設)、private置於 類的成員定義前,用來限定物件對該類成員的訪問許可權。
對於class的修飾符只可以用 public和default(預設):
public類可以在任意地方被訪問。
default類只可以被同一個包內部的類訪問。
繼承
多個類中存在相同屬性和行為時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為,只要繼承那個類即可。
此處的多個類稱為子類( 派生類),單獨的這個類稱為父類(基類或超類)。可以理解為:“子類 is a 父類"
class Subclass extends SuperClass{ }
作用:
繼承的出現減少了程式碼冗餘,提高了程式碼的複用性。
繼承的出現,更有利於功能的擴充套件。
繼承的出現讓類與類之間產生了關係,提供了多型的前提。注意:
不要僅為了獲取其他類中某個功能而去繼承;
子類 繼承(extends) 父類,就繼承了父類的(非私有)方法和屬性;(繼承了父類的所有方法和屬性,但不能直接訪問私有成員變數和方法--擁有但無法使用)
在子類中,可以使用父類的方法和數屬性,也可以建立新的資料和方法,繼承是對父類的“拓展”;
一個子類只能繼承一個父類;一個父類可以派生出多個子類;(一個爸爸,多個兒子)
方法重寫:
在子類中可以根據需要對從父類中繼承來的方法進行改造,也稱為方法的重置、覆蓋。在程式執行時,子類的方法將覆蓋父類的方法
要求:
- 子類重寫的方法必須和父類被重寫的方法具有相同的方法名稱、引數列表
- 子類重寫的方法的返回值型別不能大於父類被重寫的方法的返回值型別
- 子類重寫的方法使用的訪問許可權不能小於父類被重寫的方法的訪問許可權
子類不能重寫父類中宣告為private許可權的方法- 子類方法丟擲的異常不能大於父類被重寫方法的異常
- 若繼承的是static方法 則不是重寫,因為static方法是屬於類的,子類無法覆蓋父類的方法。
多型
物件的多型性:【只適用於方法,不適用屬性】父類的引用指向子類的物件。(父類型別可以有多個子類物件例項)
Java的引用變數有兩個型別:編譯時型別(左邊) 和 執行時型別(右邊):
編譯時型別由宣告該變數時使用的型別決定,執行時型別由實際賦給該變數的物件決定;
若編譯時型別和執行時型別不一致 , 就出現了物件的多型性
多型情況下,左邊是父類的引用,右邊是子類的物件
1.為什麼使用多型:
增加程式碼的拓展性。 (Master類方法只要呼叫父類,就可以實現對子類的所有訪問,從而不需要一直在Master中增加子類的方法,直接增加父類的子類)。2.什麼是多型:
同一種事物,條件不同產生的結果也不同
同一個引用型別,使用不同的例項而執行不同的操作。3.多型的使用:(虛擬方法呼叫)
因為宣告的是父類物件,所以使用的只能是父類中的方法,不能訪問子類中新增的屬性和方法。
當呼叫子類父類同名引數的方法時,實際執行的是子類重寫的方法子類可以看作是特殊的父類,所以父類型別的引用可以指向子類的物件:向上轉型
//使用父類型別減少子類方法的過載,
public void feed(Animal animal){
animal.eat();
animal.shout();
}
wo.feed(new Dog); //傳入不同的子類例項可以實現不同型別的方法
wo.feed(new Cat);
public void method(Object obj){ //可以傳入任意型別的物件,
}
//父類作為方法返回值, 根據id動態獲取不同的物件
public Animal getAnimal(int id){
switch(id){
case 1: return new Dog();
case 2: return new Cat();
}
}
wo.getAnimal(1);
物件型別轉換(Casting):
自動型別轉換:子類自動轉為父類(向上轉型)
強制型別轉換:父類到子類需要強轉(向下轉型)
instanceof
a instanceof A //判斷a是否能強制轉換為A型別
父類強轉為子類後,就可以使用子類的特有方法了。
--- 無繼承關係的引用型別間的轉換是非法的
抽象
abstract抽象類與抽象方法
隨著繼承層次中一個個新子類的定義,類變得越來越具體,而父類則更一般,更通用。類的設計應該保證父類和子類能夠共享特徵。有時將一個父類設計得非常抽象,以至於它沒有具體的例項,這樣的類叫做抽象類
用abstract關鍵字來修飾一個類,這個類叫做抽象類。
用abstract來修飾一個方法,該方法叫做抽象方法。
抽象方法:只有方法的宣告,沒有方法的實現。以分號結束:
public abstract void talk();
含有抽象方法的類必須被宣告為抽象類。 抽象類不能被例項化。抽象類是用來被繼承的,抽象類的子類必須重寫父類的抽象方法,並提供方法體。若沒有重寫全部的抽象方法,仍為抽象類。
abstract只能修飾 非final(可繼承),非私有,非靜態的方法,類;
不能用abstract修飾 變數、程式碼塊、構造器;
不能用abstract修飾 私有方法、靜態方法、final的方法、final的類。
抽象類的匿名子類:
public static void method(Person p){ //Person是一個抽象類
p.eat();
}
Worder woker = new Worker(); //Worker是Person的子類
method(woker); //非匿名的類非匿名的物件
method(new Worker()); //非匿名的類匿名的物件
Person p = new Person(){ //匿名子類p
pupblic void eat(){}; //重寫抽象方法
}
method(new Person(){ //匿名子類的匿名物件
public void eat(){};
});
介面
interface 介面
Java中不支援多繼承,有了介面 就可以得到多重繼承,將幾個類抽取出一些共同的行為特徵,而它們又沒有 is-a 的關係。 實現了介面就相當於得到該介面的功能。
介面的使用:
class AA extends B implements CC,DD
介面使用 interface 來定義
Java中,介面和類是兩個並列結構
介面中不能定義構造器,意味著介面不能例項化
類 implement 介面 來實現介面 (一個類可以實現多個介面 用 , , 隔開)
實現類必須覆蓋介面中的所有抽象方法,否則此實現類仍為抽象類
介面與介面之間可以多繼承如何定義介面:定義介面的成員
JDK7及以前: 只能定義 全域性常量和抽象方法 (預設)
所有成員變數預設為:全域性常量 public static final
所有抽象方法預設為:抽象方法 public abstract
JDK8:新增了 靜態方法,預設方法 (略)
> 介面中定義的靜態方法,只能通過介面來呼叫
> 預設方法,通過實現類的物件 來呼叫(預設方法可以重寫)
> 方法名,引數衝突時:
-
介面的具體使用,體現**多型性 **
Network net = new Server();
interface NetWork{xxx} class Server implement NetWor{xxx} Network net = new Server(); //介面型別,實現類物件
-
介面,實際上可以看作是一種規範
面試題:
-
介面中的 變數都是常量,是不可以改變的。
-
子類(實現類) 繼承的 父類和介面中聲明瞭同名同參的方法,預設呼叫父類中的方法--類優先原則
關鍵字
this
定義:
它在方法內部使用,即這個方法所屬物件的引用;
它在構造器內部使用,表示該構造器正在初始化的物件。
this 可以呼叫類的屬性、方法和構造器this 代表著這個類
使用:
a. 在任意方法或構造器內,通常新增this代表成員變數或成員方法(習慣省略this)
b. 當形參與成員變數同名時,必須新增this來標識該變數為成員變數
c. 使用this訪問屬性和方法時,如果在本類中未找到,會從父類中查詢
d. this可以作為一個類中構造器相互呼叫的特殊格式
this(引數列表)
super
使用super來呼叫父類中的(非私有) 屬性,方法,構造器;
尤其當子父類出現同名成員時,可以用super表明呼叫的是父類中的成員
super的追溯不僅限於直接父類
super和this的用法相像,this代表本類物件的引用,super代表父類的記憶體空間的標識
呼叫父類構造器時:(注意)
子類中所有的構造器 預設都會訪問父類中 空引數的構造器
當父類中沒有空引數的構造器時,子類的構造器必須通過 this(引數列表)或者super( 引數列表) 語句指定呼叫本類或者父類中相應的構造器。同時,只能”二選一”,且必須放在構造器的首行
如果子類構造器中既未顯式呼叫父類或本類的構造器,且父類中又沒有無參的構造器,則編譯出錯
package
package語句作為Java原始檔的第一條語句,指明該檔案中定義的類所在的包。(若預設該語句,則指定為無名包)。 它的格式為:package 頂層包名. 子包名;
包對應於檔案系統的目錄,package 語句中,用 “ .” 來指明包( 目錄) 的層次;
包通常用小寫單詞標識。通常使用所在公司域名的倒置:com.atguigu.xxx包的作用:
包幫助管理大型軟體系統:將功能相近的類劃分到同一個包中。比如: MVC的設計模式
包可以包含類和子包,劃分專案層次,便於管理
解決類命名衝突的問題
控制訪問許可權
JDK中主要的包:
import
為使用定義在不同包中的Java類,需用import語句來引入指定包層次下所需要的類或全部類(.*)。import語句告訴編譯器到哪裡去尋找類
語法格式:
import 包名.類名;
注意:
- 在原始檔中使用import顯式的匯入指定包下的類或介面
- 宣告在包的宣告和類的宣告之間。
- 如果需要匯入多個類或介面,那麼就並列顯式多個import語句即可
- 舉例:可以使用java.util.*的方式,一次性匯入util包下所有的類或介面。
- 如果匯入的類或介面是java.lang包下的,或者是當前包下的,則可以省略此import句。
- 如果在程式碼中使用不同包下的同名的類。那麼就需要使用類的全類名的方式指明呼叫的是哪個類。
- 如果已經匯入java.a包下的類。那麼如果需要使用a包的子包下的類的話,仍然需要入。
- import static組合的使用:呼叫指定類或介面下的靜態的屬性或方法
static
對於某個類來說,當我們希望某些特定的資料在記憶體空間裡只有一份,給這個類中的所有的物件共享時 而不必再給每一個物件單獨分配。使用static修飾變數,使得變數不歸某個物件所有而是大家共享。
static關鍵字的使用:
static:靜態的,可以用來修飾: 屬性,方法,程式碼塊,內部類
屬性 非靜態屬性: 每個物件獨有自己的變數 (例項變數) 。
靜態屬性: static修飾的屬性(類屬性), 多個物件共享一個靜態變數。
方法 非靜態方法:非靜態方法隨著物件的產生而建立,可以呼叫靜態屬性,方法。
靜態方法:靜態方法中不能呼叫 非靜態屬性,方法
不能使用this,super關鍵字 (類載入了物件還沒有建立)對於被static修飾後的成員:
隨著類的載入而載入
優先於物件存在
修飾的成員,被所有物件所共享
訪問許可權允許時,可不建立物件,直接被類呼叫
什麼時候要宣告為static?
屬性:
屬性是可以被多個物件所共享的,不會隨著物件的不同而不同的。
類中的常量也常常宣告為static方法:
操作靜態屬性的方法,通常設為static
工具類中的方法,通常宣告為static(Math, Arrays, Collections 等 )
main()的語法
main( )方法作為程式的入口
main( )方法也是普通的靜態方法
main( )方法可以作為我們與控制檯互動的方式。(之前使用Scanner)
final
final最終的,可以用來修飾:類,方法,變數
final修飾類:此類不能被其他類繼承。(String類, System類, StringBuffer類)
final修飾方法: 表明此犯法不可以被重寫。(Object類中的getClass() )
final修飾變數:此時的“變數” 就稱為 常量:
- final修飾屬性:
可以賦值的位置:顯示初始化, 程式碼塊初始化, 構造器中初始化。
(即 要在物件建立之前 初始化,物件例項化之後就不能改變了)- final修飾區域性變數:
final可以修飾形參,使該形參只能被使用而不能被修改(呼叫該方法時給形參賦值)。static final 用來修飾屬性:全域性常量
常用類
Object類
Object類是所有Java類的根父類
如果在類的宣告中未使用extends關鍵字指明其父類,則預設父類為java.lang.Object類
Object類只聲明瞭一個空參的構造器, 且沒有屬性。
Object類的方法:
equals(),toString()
clone(): 克隆物件
finalize(): 在物件被回收之前,會先呼叫此方法。
getClass(),hashCode(),wait(), notify(), notifyAll()
==equlse 與 的區別:(面試題)
==:運算子
可以使用在基本型別和引用資料型別變數中
若比較的是基本型別,則比較的是變數儲存的資料(不一定要型別相同,運算子會自動轉型)
若比較的是引用型別,則比較兩個物件的地址值equals( ):
只是一個方法,而非運算子;只能適用於引用型別;(所有物件都繼承Object類,都有equals())
在Object類中,equals() 和 == 的作用是相同的,比較兩個物件的地址值
像 String,Date,File,包裝類 等都重寫了Object類中的equals()方法。重寫後,比較的是物件的“實體內容”
若自定義類適用equals()方法,(比較“實體內容--物件的屬性值”)也需要自己進行重寫。
toString()
Object類中的toString():
輸出物件引用時,實際上就是呼叫當前物件的toString( )
Object類中的toString():會輸出類名+@+雜湊地址
像String,Date,File,包裝類等都重寫了Object類中的toString()方法,輸出""實體內容"
對於自定義類來說,也可以自己重寫toStrig方法
包裝類
將八大基本資料型別 定義相關的引用型別--包裝類,使其擁有類的特點,能呼叫類中的方法。
Integer中快取有-128~127的數,超過127時需要new
基本資料型別 --》包裝類:呼叫包裝類的構造器
//1.包裝類的預設初始化值為null
//2.包裝類可以傳入對應基本資料型別,也可以傳入 字串(格式要正確)
Boolean b1 = new Boolean("TruE"); //true 忽略大小寫
Boolean b2 = new Boolean("true123"); //false
包裝類 --》基本資料型別: .xxxValue( )
int i = 1;
Integer in = new Integer(i);
int i2 = in.intValue(); //.xxxValue();
-
自動裝箱與拆箱
JDK1.5新特性:Java會自動對 基本資料型別 與 包裝類 之間進行轉換
//自動裝箱: boolean b1 = true; Boolean b2 = b1; //自動將 基本資料型別---> 包裝類 //自動拆箱 int b3 = b2; //自動將 包裝類--> 基本資料型別
-
基本資料型別,包裝類 ---> String型別
String.valueOf( xxx )
//1.連線運算 String str1 = num1+""; //2.String過載的 .ValueOf()方法 String str2 = String.valueOf(num1);
-
String型別 ---> 基本資料型別,包裝類
parseXxx()
//呼叫包裝類的parseXxx(); int num2 = Integer.parseInt(str1);