
Java是面向對象的高級編程語言,面向對象的特征如下:
- 面向對象具有抽象、封裝、繼承、多態等特性;
- 面向對象可以將復雜的業務邏輯簡單化,增強代碼復用性;
- 面向對象是一種常見的思想,比較符合人們的思考習慣。
面向過程和面向對象是什么?
- 面向過程是一種讓程序的功能按照先后順序執行的編程思想,每一個功能都是一步一步實現。
- 面向對象是將功能、事務高度抽象化的編程思想,把問題拆分成很多步驟,每一步都要進行抽象,從而形成對象。程序通過對不同對象的調用,以此來解決問題。
二者的區別
- 面向過程是自頂向下的設計模式,要考慮到每一個模塊應該要分解成那些子模塊,每一個子模塊還要分解成更小的子模塊,直到把模塊分解為一個一個的函數(或者方法)。面向過程的最小程序單元是函數,每一個函數負責完成一個功能,用于接收輸入的數據,函數進行處理,輸出結果。
- 面向對象最小的程序單元是類,當軟件系統龐大起來的時候,易于維護
對象 & 類
在大多面向對象的語言都使用 class
定義類,類就是對一系列對象的抽象,類好比是快遞的外包裝,類里面的內容就表示這個對象的定義;例如:
class Javaer{}
這樣,就聲明了一個名字為 Javaer
的類,在 Java 中,可以通過 new 來創建這個對象:
Javaer javaer = new Javaer();
在 Java 中,類的命名要遵守駝峰命名法,
駱駝式命名法(Camel-Case)又稱駝峰式命名法,是電腦程式編寫時的一套命名規則(慣例)。正如它的名稱 CamelCase 所表示的那樣,是指混合使用大小寫字母來構成變量和函數的名字。程序員們為了自己的代碼能更容易的在同行之間交流,所以多采取統一的可讀性比較好的命名方式。
創建對象
在使用 Java 過程中,都是和對象打交道,Java 中的一切都可以看作是對象,但盡管如此,我們操作對象,卻是對對象的引用:
Door key;
如上代碼中創建的只是引用,并非一個對象;如果要想正確的創建一個對象并且不在編譯的過程中出錯,那么就需要在創建對象引用時同時把一個對象賦給它。
Door key = new Door();
在 Java 中,一旦創建了一個引用,就希望它能與一個新的對象進行關聯,通常使用 new
操作符來實現這一目的。
屬性和方法
對于一個類,最基本的要素就是要有屬性和方法;
屬性又被稱為字段:
Class Door {int a;Key key;
}
方法就是函數,方法的基本組成包括 方法名稱、參數、返回值和方法體
public int get(){return 1;
}
在上面的示例中,get
是方法名稱,()
內是方法接收的參數、return
后面的是方法的返回值(如果是 void 方法,則不需要返回值),{}
里的是方法體。
構造方法
Java 中有一種特殊的構造方法,又被叫做構造器或者構造函數。構造方法就是在創建對象的時候保證每個對象都被初始化。構造方法只在創建對象的時候調用一次,它沒有參數類型和返回值,它的名稱要和類名保持一致,并且構造方法可以有多個。
Class Door{int number;String color;public Door(){}public Door(int number){}public Door(String color){}public Door(int number,String color){}
}
上述代碼中定義了一個 Door
類,但是卻沒有參數類型和返回值,而且有多個同名的 Door() 方法,每個方法的參數列表也不同,其實這是面向對象特征--多態的體現,后文會介紹。定義好構造方法后,我們就能創建一個 Door
對象了。
class creatDoor{public static void main(String[] args) {Door door1 = new Door();Door door2 = new Door(1);Door door3 = new Door("yellow");Door door4 = new Door(1,"yellow");}
}
如果類中沒有定義任何構造方法,那么 JVM 會為你自動生成一個構造方法,如下
class Door {int number;String color;}class createDoor {public static void main(String[] args) {Door door1 = new Door();}
}
默認的構造方法也被稱為默認構造器或者無參構造器。
這里需要注意一點的是,即使 JVM 會為你默認添加一個無參的構造器,但是如果你手動定義了任何一個構造方法,JVM 就不再為你提供默認的構造器,你必須手動指定,否則會出現編譯錯誤。
方法重載
重載在 Java 中是一個很重要的概念,它是類名的不同表現形式,上文提到的構造函數,是重載的一種,另外一種重載就是方法的重載。
public class Door {int number;String color;public Door(){}public Door(int number){}public int getDoor(int number){return 1;}public String getDoor(String color){return "color";}}
如上所示,有 Door 的構造函數的重載,也有 getDoor 方法的重載。
如果有幾個相同的方法名字,Java 如何知道你調用的是哪個方法呢?
每個重載的方法都有獨一無二的參數列表。其中包括參數的類型、順序、參數數量等,滿足一種一個因素就構成了重載的必要條件:
- 方法名稱必須相同。
- 參數列表必須不同(個數不同、或類型不同、參數類型排列順序不同等)。
- 方法的返回類型可以相同也可以不相同。
- 僅僅返回類型不同不足以成為方法的重載。
- 重載是發生在編譯時的,因為編譯器可以根據參數的類型來選擇使用哪個方法。
方法的重寫
方法重寫的描述是對子類和父類
之間的。
而重載是發生同一個類中的。
class Food {public void eat(){System.out.printl('eat food');}
}class Fruit extends Food{@Overridepublic void eat(){System.out.printl('eat fruit');}
}
上述代碼中,就含有重寫的范例,子類 Fruit 中的方法和父類 Food 的方法相同
所以重寫的標準是:
- 重寫的方法必須要和父類保持一致,包括返回值類型,方法名,參數列表 也都一樣。
- 重寫的方法可以使用
@Override
注解來標識 - 子類中重寫方法的訪問權限不能低于父類中方法的訪問權限。
初始化
我們在創建一個對象,使用 new 創建對象的時候,實際上是調用了這個對象無參數的構造方法進行的初始化。這個無參數的構造函數可以隱藏,由 JVM 自動添加。也就是說,構造函數能夠確保類的初始化。
class Door{public Door(){}
}
成員初始化
成員初始化有兩種形式
- 編譯器默認指定的字段初始化,基本數據類型的初始化
類型 | 初始值 |
---|
其他數據類型,如 String ,其初始值默認為 null
- 指定數值的初始化
int a = 11;
構造器初始化
構造器可以用來對某些方法和某些動作進行初始化,從而確定初始值:
public class Number{int n;public Number(){n = 11;}
}
利用構造函數,能夠把 n 的值初始化為 11。
初始化順序
- 靜態屬性:static 開頭定義的屬性
- 靜態方法塊: static {} 包起來的代碼塊
- 普通屬性: 非 static 定義的屬性
- 普通方法塊: {} 包起來的代碼塊
- 構造函數: 類名相同的方法
- 方法: 普通方法
要驗證初始化的順序,最好的方法就是寫代碼實踐得到
public class TheOrder {// 靜態屬性private static String staticField = getStaticField();// 靜態方法塊static {System.out.println(staticField);System.out.println("靜態方法塊初始化");}// 普通屬性private String field = getField();// 普通方法塊{System.out.println(field);}// 構造函數public TheOrder() {System.out.println("構造函數初始化");}public static String getStaticField() {String statiFiled = "Static Field Initial";return statiFiled;}public static String getField() {String filed = "Field Initial";return filed;}// 主函數public static void main(String[] argc) {new TheOrder();}
}
最終輸出結果為
Static Field Initial
靜態方法塊初始化
Field Initial
構造函數初始化
那么初始化順序就是:
- 靜態屬性初始化
- 靜態方法塊初始化
- 普通屬性初始化
- 普通方法塊初始化
- 構造函數初始化
this 和 super
this
和 super
都是 Java 中的關鍵字
this
this
這個關鍵字只能用在方法的方法體內。當一個對象創建后,JVM 就會給這個對象分配引用自己的指針,這個指針的名字就叫做 this
。
this
表示的當前的對象,可以用來調用方法、屬性以及對象本身。
public class Test{private int number;private String username;private String password;private int x = 100;public Test(int n){number = n;//這個可以寫為this.number = n;}public Test(int i, String username, String password){//成員變量和參數同名,成員變量被屏蔽,用"this.成員變量"的方式訪問成員變量.this.username = username;this.password = password;}//默認不帶參數的構造方法public Test(){this(0, "未知", "空");//通過this調用另外一個構造方法. }public Test(String name){this(1, name, "空");/** 通過this調用另外一個構造方法. 雖然上面的兩種構造方法都是編譯通過的。但是并沒有實際的意義。一般我們會在參數多的構造函數里面去用this調用參數少的構造函數(并且只能放在方法體里面的第一行)。* 示例里面的這種構造方法就相當于給了三個參數(其中兩個參數已經定了,另一個參數在這個構造方法傳入)。 */}public Test(int i, String username){this(i, username, null);//通過this調用另外一個構造方法. }}
this
使用方法總結
- 通過 this 調用本類中另一個構造方法,用法是 this (參數列表),這個僅僅在類的構造方法中可以用,并且只能放在類的構造方法的方法體的第一句。別的地方不能用。
- 方法參數或者方法中的局部變量和成員變量同名的情況下,成員變量被屏蔽,此時要訪問成員變量則需要用 “this.變量名”的方式來引用變量。但是,在沒有同名的情況,可以直接用成員變量的名字,而不用 this ,用了也是正確的(看起來更加的直觀)。
- 在方法中,需要引用該方法所屬類的當前對象的時候,直接用 this.
super
- 在子類的構造方法中要調用父類的構造函數,用 “super(參數列表)”的方式調用,參數不是必須的(如果子類的構造方法沒有某個參數,但是父類的構造函數里面有這個參數,那么在 super() 的參數列表里的相應位置,傳入參數 null )。
- 如果子類重寫了父類的某一個方法。(也就是子類和父類有相同的方法定義,但是有不同的方法體),此時,我們可以通過 "super.成員方法名"來調用父類里面的這個方法。
面向對象三大特性
封裝
封裝在 Java 中又稱訪問控制權限,訪問控制權限其實最核心就是一點:只對需要的類可見。
Java 中成員的訪問權限共有四種,分別是 public、protected、default、private,它們的可見性如下
同一個類 | 同一個包 | 不同包的子類 | 不同包的非子類 |
---|
繼承
繼承是所有 OOP 語言和 Java 語言不可缺少的組成部分。
繼承是 Java 面對對象編程技術的一塊基石,是面對對象的三大特征之一,也是實現軟件復用的重要手段,繼承可以理解為一個對象從另一個對象獲取屬性的過程。
當我們準備編寫一個類時,發現某個類已有我們所需要的成員變量和方法,假如我們想復用這個類的成員變量和方法,即在所編寫類中不用聲明成員變量就相當于有了這個成員變量,不用定義方法就相當于有了這個方法,那么我們可以將編寫的類聲明為這個類的子類即繼承。
源類,基類,超類或者父類都是一個概念導出類,繼承類,子類也都是同一個概念
繼承中最常使用的兩個關鍵字是 extends 和 implements 。
這兩個關鍵字的使用決定了一個對象和另一個對象是否是 IS-A (是一個)關系。
通過使用這兩個關鍵字,我們能實現一個對象獲取另一個對象的屬性。
所有 Java 的類均是由 java.lang.Object 類繼承而來的,所以 Object 是所有類的祖先類,而除了 Object 外,所有類必須有一個父類。
繼承的語法
class Father{private int i;protected int j;public void func(){}
}class Son extend Father{public int k;public void func(){}
}
注意:如類聲明語句中沒有extends子句,則該類為java.lang包中的Object的子類。這就說明了java中的代碼其實都有一個繼承的關系,只不過是繼承Object這個java中最根本的父類。
繼承的特點
- 子類擁有父類非private的屬性和方法,子類繼承的父類方法和成員變量可以當作自己的方法和成員變量一樣被子類的實例方法使用。
- 子類可以有自己屬性和方法,即子類可以在父類的基礎上對父類進行擴展。
- 子類可以用自己的方式實現父類的方法。(重寫或者覆蓋)
多態
多態是同一個行為具有多個不同表現形式或形態的能力。多態就是同一個接口,使用不同的實例而執行不同操作。
多態性是面向對象編程的又一個重要特征,它是指在父類中定義的屬性和方法被子類繼承之后,可以具有不同的數據類型或表現出不同的行為,這使得同一個屬性或方法在父類及其各個子類中具有不同的含義。 對面向對象來說,多態分為編譯時多態和運行時多態。其中編譯時多態是靜態的,主要是指方法的重載,它是根據參數列表的不同來區分不同的方法。通過編譯之后會變成兩個不同的方法,在運行時談不上多態。而運行時多態是動態的,它是通過動態綁定來實現的,也就是大家通常所說的多態性。
e.g. 彩色打印機和黑白打印機都是打印機,但是他們打印出來的東西顏色不一樣。這就是多態
多態實現的條件
- 繼承:在多態中必須存在有繼承關系的子類和父類。
- 重寫:子類對父類中某些方法進行重新定義,在調用這些方法時就會調用子類的方法,如果沒有重寫的話在父類中就會調用。當子類對象調用重寫的方法時,調用的是子類的方法,而不是父類中被重寫的方法。要想調用父類中被重寫的方法,則必須使用關鍵字 super。
- 向上轉型:在多態中需要將子類的引用賦給父類對象,只有這樣該引用才既能可以調用父類的方法,又能調用子類的方法。
public class Person {public void drink(){System.out.println("喝咖啡");}public void eat(){System.out.println("吃點東西");}
}public class Man extends Person {public void drink(){System.out.println("喝冰美式");}
}public class Test {public static void main(String[] args) {Person p = new Man();p.drink();p.eat();}
}
輸出:
喝冰美式
吃點東西
多態的優點
- 消除類型之間的耦合關系
- 可替換性
- 可擴充性
- 接口性
- 靈活性
- 簡化性
接口和抽象類
接口
接口(英文:Interface),在 Java 編程語言中是一個抽象類型,是抽象方法的集合,接口通常以 interface
來聲明。一個類通過繼承接口的方式,從而來繼承接口的抽象方法。
接口并不是類,編寫接口的方式和類很相似,但是它們屬于不同的概念。類描述對象的屬性和方法。接口則包含類要實現的方法。除非實現接口的類是抽象類,否則該類要定義接口中的所有方法。
接口無法被實例化,但是可以被實現。一個實現接口的類,必須實現接口內所描述的所有方法,否則就必須聲明為抽象類。
接口和類相似點:
- 一個接口可以有多個方法。
- 接口文件保存在.java結尾的文件中,文件名使用接口名。
- 接口的字節碼文件保存在.class結尾的文件中。
- 接口相應的字節碼文件必須在與包名稱相匹配的目錄結構中。
接口和類的區別:
- 接口不能用于實例化對象。
- 接口沒有構造方法。
- 接口中所有的方法必須是抽象方法。
- 接口不能包含成員變量,除了static和final變量。
- 接口不是被類繼承了,而是要被類實現。
- 接口支持多重繼承。
接口的聲明
[可見度] interface 接口名稱 [extends 其他類名]{// 聲明變量// 抽象方法
}
范例:
public interface TestInterface{public void eat();public void drink();
}
接口的實現
類實現接口的時候,類要實現接口中所有的方法。否則,類必須聲明為抽象的類。
范例:
public class Test implements TestInterface{public void eat(){System.out.println("吃東西");}public void drink(){System.out,println("喝水");}public static void main(String args[]){Test t = new Test();t.eat();t.drink();}
}
在實現接口的時候,要注意:
- 一個類可以同時實現多個接口。
- 一個類只能繼承一個類,但是能實現多個接口。
- 一個接口能繼承另一個接口,這和類之間的繼承比較相似。
抽象類
一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。
抽象類除了不能實例化對象之外,類的其它功能依然存在,成員變量、成員方法和構造方法的訪問方式和普通類一樣。
由于抽象類不能實例化對象,所以抽象類必須被繼承,才能被使用。也是因為這個原因,通常在設計階段決定要不要設計抽象類。
父類包含了子類集合的常見的方法,但是由于父類本身是抽象的,所以不能使用這些方法。
在 Java 中用 abstract class 來定義抽象類。
public interface Test {void ColorTest();}abstract class WhiteDog implements Dog{public void ColorTest(){System.out.println("Color is white");}abstract void MMiniTest();
}
在抽象類中,具有如下特征
- 如果一個類中有抽象方法,那么這個類一定是抽象類,也就是說,使用關鍵字
abstract
修飾的方法一定是抽象方法,具有抽象方法的類一定是抽象類。實現類方法中只有方法具體的實現。 - 抽象類中不一定只有抽象方法,抽象類中也可以有具體的方法,你可以自己去選擇是否實現這些方法。
- 抽象類中的約束不像接口那么嚴格,你可以在抽象類中定義 構造方法、抽象方法、普通屬性、方法、靜態屬性和靜態方法
- 抽象類和接口一樣不能被實例化,實例化只能實例化
具體的類