1.面向對象的主要特征
- 抽象:抽象就是忽略一個主題中與當前目標無關的那些方面,以便更充分地注意與當前目標有關的方面。抽象包括兩個方面:一個是過程抽象;二是數據抽象。
- 繼承:繼承是一種聯結類的層次模型,并且允許和鼓勵類的重用,他提供了一種明確表述共性的方法。對象的一個新類可以從現有的類中派生,這個過程稱為類繼承。新類(派生類,也稱子類)繼承了原始類(基類,也稱父類)的特性。派生類繼承了基類的方法和實例變量,并且派生類可以修改或增加新的方法,使之更適合特殊的需求。
- 封裝:封裝就是指客觀事物抽象成類,每個類對自身的數據和方法實行保護。類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。
- 多態:多態是指允許不同類的對象對同一消息做出響應。多態包括參數化多態和包含多態。多態性語言具有靈活、抽象行為共享、代碼共享等優勢,很好地解決了應用程序函數同名問題。
2.繼承和組合
組合式指在新類里面創建原有類的對象,重復利用已有類的功能。繼承允許設計人員根據其他類的實現來定義一個類的實現。組合都允許在新的類中設置子對象。組合和繼承存在著對應關系:組合中的整體類和繼承中的子類對應,組合中的局部類和繼承的父類對應。
二者區別:如,Car表示汽車對象,Vehicle表示交通工具對象,Tire表示輪胎對象。Car是Vehicle的一種,因此是一種繼承關系(又被稱為"is - a"關系);而Car包含了多個Tire,因此是一種組合關系(又被稱為"has - a"關系):
繼承 | 組合 |
---|---|
class Vehicle{} class Car extends Vehicle{} |
class Tire{} class Car extends Vehicle{ private Tire t = new Tire(); } |
選擇:
- 除非兩個類之間是"is - a"的關系,否則不要輕易地使用繼承。當父類被修改時,會影響到所有繼承自它的子類,從而增加程序的維護難度與成本。
- 如果類之間沒有"is - a"的關系,可以通過接口與組合方式來達到相同的目的。策略設計模式采用接口與組合的方式比采用繼承的方式具有更好的可擴展性。
能使用組合就盡量不要使用繼承。
3.多態的實現機制
表示當同一個操作作用在不同對象時,會有不同的語義,從而會產生不同的結果。多態主要有兩種表現方式:
- 方法的重載(overload):重載是指同一個類中有多個同名的方法,但這些方法有著不同的參數(參數個數和類型、順序都不相同),因此在編譯時就可以確定到底調用哪個方法,它是一種編譯時多態。重載可看作一個類中的方法多態性。
- 方法的覆蓋(override):子類可以覆蓋(重寫)父類的方法,因此同樣的方法(參數個數和類型都相同)會在父類與子類又不同的表現形式。在Java中,基類的引用變量不僅可以指向基類的實例對象,也可以指向其子類的實例對象。同樣,接口的引用變量也可以指向其實現類的實例對象。而程序調用的方法在運行期才動態綁定(綁定指的是將一個方法調用和一個方法主體連接到一起),就是引用變量所指向的具體實例對象的方法。只有在運行時才決定調用哪個方法,因此被稱為運行時多態。如下例:
class Base{
public Base() {
g();
}
public void f(){
System.out.println("Base f()");
}
public void g(){
System.out.println("Base g()");
}
}
class Derived extends Base{
public void f(){
System.out.println("Derived f()");
}
public void g(){
System.out.println("Derived g()");
}
}
public class Test{
public static void main(String []args){
Base b = new Derived();
b.f();
b.g();
}
}
程序運行結果是:
Derived g()
Derived f()
Derived g()
注:只有類中的方法才有多態的概念,類中成員變量沒有多態的概念。成員變量的值取決于所定義變量的類型,這是在編譯期間確定的
注意:
- 對于繼承來說,如果基類方法的訪問權限為private,那么就不能在派生類對其重載;如果派生類也定義了一個同名函數,這只是一個新的方法,不會達到重載的效果。
- 派生類中的覆蓋方法的返回值必須和基類中被覆蓋的方法的返回值相同。
- 派生類中的覆蓋方法所拋出的異常必須和基類(或是其子類)中被覆蓋的方法所拋出的異常一致。
- 基類中被覆蓋的方法不能為private,否則其子類只是定義了一個方法,并沒有對其覆蓋。
重載和覆蓋的區別:
- 覆蓋是子類和父類之間的關系,是垂直關系;重載是同一個類中方法之間的關系,是水平關系。
- 覆蓋只能由一個方法或只能有一對方法產生關系;重載是多個方法之間的關系。
- 覆蓋要求參數列表相同;重載要求參數列表、類型、順序不同。
- 覆蓋關系中,調用方法體是根據對象的類型(對象對應存儲空間類型)來決定;而重載關系是根據調用時的實參表和形參表來選擇方法體的。
注:函數是不能以返回值來區分的,雖然父類和子類中的函數有著不同的返回值,但是它們有著相同的函數名,因此,編譯器無法區分。
4.抽象類和接口的異同
在Java語言中,可以把類或類中的方法聲明為abstract(只能修飾類或方法,不能修飾屬性)來表示一個類是抽象類。接口是指一個方法的集合,接口中的所有方法都沒有方法體,通過關鍵字interface來實現。
- 只要包含一個抽象方法的類就必須被生命為抽象類,抽象類可以聲明方法的存在而不去實現它,被聲明為抽象的方法不能包含方法體。
- 在實現時,必須包含相同的或者耕地的訪問級別(public>protected>private)。
- 抽象類在使用過程中,不能被實例化,但是可以創建一個對象使其指向具體子類的一個實例。
- 抽象類的子類為父類中的所有抽象方法提供具體的實現,否則它們也是抽象類。
- 接口中的所有方法都是抽象的,可以通過接口來間接實現多重繼承。
- 接口中的成員變量都是static final類型。
- 由于抽象類可以包含部分方法的實現,因此,抽象類比接口有更多的優勢。
接口與抽象類的不同點:
- 接口只有定義,其方法不能在接口中實現,只有實現接口的類才能實現接口中定義的方法,而抽象類可以定義域實現某個方法。
- 接口需要實現(implements),但抽象類只能被繼承(extends)。
- 接口強調特定功能的實現,其設計理念是"has - a"關系;而抽象類強調所屬關系,其設計理念為"is - a"關系。
- 接口中定義成員變量默認為public static final,只能夠有靜態的不能被修改的數據成員,而且必須賦值,其所有成員方法都是public、abstract的,而且只能被這兩個關鍵字修飾。而抽象類可以有自己的數據成員變量,也可以有非抽象的成員方法,而且,抽象類中的成員變量默認是default,也可以定義private、protected和public,這些成員變量可以在子類中被重新定義,也可以被重新賦值,抽象類中的抽象方法(abstract修飾)不能用private、static、synchronized、native等訪問修飾符修飾,同時方法必須以分號結尾,并且不帶花括號。當功能需要累積時,用抽象類;不需要累積時,用接口。
- 接口被運用于實現畢竟常用的功能,便于日后維護或添加刪除方法;而抽象類更傾向于充當公共類的角色,不適合用于日后重新對里面的代碼進行修改。
注:
- 抽象類多用在同類事物中又無法具體描述的方法的場景,所以當子類和父類之間存在有邏輯上的層次結構是,使用抽象類;
- 接口多用在不同類之間,定義不同類之間的通信規則,所以當希望支持差別較大的兩個或者更多對象之間的特定交互行為時,應該使用接口。
- 接口可以繼承接口,抽象類可以實現接口,抽象類也可以繼承具體類。抽象類也可以有靜態的main方法。
5.內部類
主要有4種:靜態內部類、成員內部類、局部內部類、匿名內部類。
- 靜態內部類是指被聲明為static的內部類,不依賴于外部類實例而被實例化。不能跟外部類有相同的名字,不能訪問外部類的普通成員變量,只能訪問外部類的靜態成員和方法(包括私有類型)。
- 成員內部類可以自由地引用外部類的屬性和方法,無論這些屬性和方法是靜態的還是非靜態的。它與一個實例綁定在了一起,不可以定義靜態的屬性和方法。只有在外部的類被實例化后,這個內部類才能被實例化。非靜態內部類中不能有靜態成員。
- 局部內部類像局部變量一樣,不能被public、protected、private以及static修飾,只能訪問方法中定義為final類型的局部變量。
- 匿名內部類是一種沒類名的內部類,必須繼承其他類或者實現其他接口。
5.this與super區別
在Java語言中,this用來指向當前實例對象,它的一個非常重要的作用就是用來區分對象的成員變量與方法的形參。(如:this.name = name)。
super可以用來訪問父類的方法或成員變量。當子類的方法或成員變量與父類有相同的名字時也會覆蓋父類的方法或成員變量,要想訪問父類的方法或成員變量只能通過super關鍵字來訪問。
注:當子類構造函數需要顯示調用父類構造函數時,super()必須為構造函數中的第一條語句。
6.continue、break、return
- continue用于停止當次循環,回到循環起始處,進入下一次循環操作。
- break用于直接強行跳出當前循環,不在執行剩余代碼。如下代碼可跳出多層循環:
public class Break {
public static void main(String args[]){
out:
for(int i = 0;i < 5; i++) {
for(int j=0;j<5;j++) {
if(j>=2) {
break out;
System.out.println(j);
}
}
}
}
}
7.final、finally、finalize
- final用于聲明屬性、方法和類,分別表示屬性不可變、方法不可覆蓋和類不可被繼承(不能再派生出新的子類)。
被final修飾的變量不可變:一是引用不可變;二是對象不可變。final指的是引用的不可變性,即它只能指向初始時指向的那個對象,而不關心指向對象內容的變化。所以,被final修飾的變量必須被初始化。初始化方式:- 在定義時初始化;
- final成員變量可以在初始化塊中初始化,但不可在靜態初始化塊中初始化;
- 靜態final成員變量可以在靜態初始化塊中初始化,但不可在初始化塊中初始化;
- 在類的構造器重初始化,但靜態final成員變量不可在構造函數中初始化。
final方法:當一個方法聲明為final時,該方法不允許任何子類重寫這個方法,但子類仍然可以使用這個方法。另外,有一種稱為inline(內聯)機制,當調用也給被聲明為final的方法時,直接將方法主體插入到調用處,而不是進行方法調用,這樣提高程序效率。
final參數:用來表示這個參數在這個函數內部不允許修改。
final類:當一個類被聲明為final時,此類不能被繼承,所有方法不能被重寫(覆蓋)。但是要想final類的成員變量不可改變,必須給成員變量增加final修飾,不然可以改變。一個類不能被聲明為final,同時又被聲明為final。
- finally作為異常處理的一部分,它只能用在try/catch語句中,并且附帶一個語句塊,表示這段語句最終一定被執行,經常被用在需要釋放資源的情況下。
- finalize是Object類的一個方法,在垃圾回收器執行時會調用被回收對象的finalize()方法,可以覆蓋此方法實現對其他資源的回收,例如關閉文件等。
8.斷言assert
它的作用是對也給boolean表達式進行檢查,一個正在運行的程序必須保證這個boolean表達式的值為true,若boolean表達式的值為false,則說明程序已經處于一種不正確的狀態下,系統需要提供告警信息并且退出程序。一般在調試程序時使用。
有兩種表達式:1.assert expression1 與 assert expression1 : expression2,其中,expression1表示一個boolean表達式,expression2表示一個基本類型或者是一對象,基本類型包括:boolean、char、double、float、int和long。
public class Test {
public static void main(String []args){
assert 1 + 1 == 2;
System.out.println("assert1 ok");
assert 1 + 1 == 3 : "assert faild,exit."
System.out.println("assert2 ok");
}
}
當執行指令java -ea Test時,程序的輸出結果為:
assert1 ok
Exception in thread "main" Java.lang.AssertionError:assert faild,exit at Test.main(Test.java:5)
應用范圍包括:
- 檢查控制流;
- 檢查輸入參數是否有效;
- 檢查函數結果是否有效;
- 檢查程序不變量。
9.static關鍵字
主要有兩種作用:第一,為某種特定數據類型或對象分配單一的存儲空間,而與創建對象的個數無關。第二,實現某個方法或屬性與類而不是對象關聯在一起,即在不創建對象的情況下就可以通過類來直接調用方法或使用類的屬性。
- static成員變量
Java語言中,可通過static關鍵字來達到全局的效果。靜態變量屬于類,在內存中只有也給復制(所有實例都指向同一個內存地址),只要靜態變量所在的類被加載,這個靜態變量就會被分配空間,這樣就可以被使用。靜態變量只有一個,被類擁有,而實例對象是與具體對象有關的(對象的引用)。Java中,不能再方法體中定義static變量。 - static成員方法
與靜態成員變量類似,是類的方法,不需要創建對象就可以被調用。不能使用this和super關鍵字,不能調用非static方法和非static成員變量,只能訪問所屬類的靜態成員變量和成員方法。
static一個很重要的用途是實現單例模式。單例模式的特點是該類只能有一個實例,為了實現這一功能,必須隱藏類的構造函數,即把構造函數聲明為private,并提供一個創建對象的方法,由于構造對象被聲明為private,外界無法直接創建這個類型的對象,只能通過該類提供的方法來獲取類的對象,要達到這樣的目的只能把創建對象的方法聲明static,代碼如下:
class Singleton{
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance(){
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
用public修飾的static變量和方法本質上都是全局的,若在static變量前用private修飾,則表示這個變量可以在類的靜態代碼塊或者類的其他靜態成員方法中使用,但是不能在其他類中通過類名來直接引用。
static代碼塊
static代碼塊在類中是獨立于成員變量和成員函數的代碼塊的,它不再任何一個方法體內,JVM在加載類時會執行static代碼塊,如果有多個static代碼塊,JVM按順序執行。static代碼塊經常被用來初始化靜態變量,只會被執行一次。static內部類
指被聲明為static的內部類,它可以不依賴于外部類實例對象而被實例化,而通常的內部類需要在外部類實例化才能實例化。不能與外部類有相同名字,不能訪問外部類的普通成員變量,只能訪問外部類的靜態成員和靜態方法(包括私有類型)。
值傳遞與引用傳遞
- 值傳遞
在方法中調用中,實參會把它的值傳遞給形參,形參只是實參的值初始化一個臨時的存儲單元,因此形參與實參雖然有著相同的值,但是卻有著不同的存儲單元,因此對形參的改變不會影響實參的值 - 引用傳遞
在方法調用中,傳遞的是對象(也可以看做是對象的地址),這是形參與實參的對象指向同一塊存儲單元,因此對形參的修改就會影響實參的值。
在Java中,原始數據類型(int、short、long、byte、char、float、double、boolean)在傳遞參數時都是按值傳遞,而包裝類型(Integer、String、Short、Byte、Long、Float、Double,Character、Boolean為不可變量)傳遞參數時是按引用傳遞。按引用傳遞其實與傳遞指針類似,是把對象的地址作為參數的。
public class Test{
public static void changeStringBuffered(StringBuffer ss1, StringBuffer ss2) {
ss1.append(" world");
ss2 = ss1; //ss1的地址賦值給ss2了
}
public static void main(String []args){
Integer a = 1;
Integer b = a;
b++;
System.out.println(a);
System.out.println(b);
StringBuffer s1 = new StringBuffer("Hello");
StringBuffer s2 = new StringBuffer("Hello");
changeStringBuffered(s1, s2);//把s1、s2的地址傳遞給形參ss1、ss2
System.out.println(s1);
System.out.println(s2;
}
}
程序運行結果為:
1
2
Hello World
Hello //因為s2的值沒變,只是ss2的地址指向s1的了