# 狂神說Java學習筆記 : 面向對象 *** > **即使再小的帆也能遠航** >**本篇學習筆記參考自[狂神說](https://www.bilibili.com/video/BV12J41137hu/)** >###### tags: `狂神說Java` `Java基礎` *** # 什麼是面向對象 > Java 的核心思想就是OOP(面向對象) - **屬性(Attribute)** + **方法(Method)** = **類(Class)** - 面向過程 : - 第一步...,第二步... => 線性思維 ## 面相對象 - 物以類聚,**分類**的思維模式 - 思考問題需要那些分類,並個別處理 - 適合處理複雜問題、多人協作問題 - ***對於描述複雜的事物,為了從宏觀上把握、從整體上合理分析,我們需要使用面向對象的思路來分析整個系統。但是,具體到微觀操作,仍然需要面向過程的思路去處理*** - OOP : Object-Oriented Programming - **以類的方式組織代碼,以對象的方式組裝數據** - 程式運行看法 : 先有**類**後有**對象** - 抽象概念(類): 學生 - 具體事物(對象): 小明、小紅、小章... - 類是對象的模板 *** # 回顧方法 ## 方法的定義 - 修飾符 - 返回類型 - Break & Return 的差異 - **方法名**: 注意規範即可。需見名知意。 - **參數列表**: (參數類型, 參數名) ... - 異常拋出: 疑問,後面講解 ## 方法的調用 - 大原則 : 1. 被 static 修飾的內容會隨著類的加載而加載,所以靜態化的內容可以不用實例化就直接調用。 2. 同類型(靜態對靜態、非靜態對非靜態)可以互相調用。 - 靜態方法 & 非靜態方法 - 形式參數 & 實質參數 - 值傳遞 ```java= public class Demo04 { public static void main(String[] args) { int a = 1; System.out.println(a); Demo04.change(a); System.out.println(a); // 1 } // 返回值為空 public static void change(int a){ a = 10; } } ``` - 引用傳遞 ```java= // 引用傳遞: 傳遞對象,但本質還是值傳遞 public class Demo05 { public static void main(String[] args) { Person person = new Person(); System.out.println(person.name); // null Demo05.change(person); System.out.println(person.name); // Jerry } public static void change(Person person){ // person 是一個對象: 指向的 ---> Person person = new Person(); // 這是一個具體的人,可以改變屬性 person.name = "Jerry"; } } // 定義了一個 Person 類,有一個屬性: name class Person{ String name; } ``` - this 關鍵字 *** # 類與對象的創建 ```jav= // 一個項目 應該只存在一個main方法 public class Application { public static void main(String[] args) { // 類: 抽象的,實例化 // 類實例化後,會返回一個自己的對象! // student對象 就是一個 Student類 的具體實例! Student xiaoming = new Student(); Student xh = new Student(); xiaoming.name = "小明"; xiaoming.age = 3; System.out.println(xiaoming.name); System.out.println(xiaoming.age); xh.name = "小紅"; xh.age = 3; System.out.println(xh.name); System.out.println(xh.age); } } ``` ``` java= // 學生類 public class Student { // 屬性: String name; // null int age; // 0 //方法 public void study(){ System.out.println(this.name + "在學習"); } } ``` *** # 構造器詳解 - 構造器也稱為構造方法,在創建對象時(**new**)必須調用 - 若程式未定義,編譯時仍會自動生成無參構造器 - 兩個特點 : - 必須和類的名字相同 - 必須**沒有返回類型**,也不能寫void - 注意點 : 定義有參構造後,若想使用無參構造,需顯示定義一個無參構造 ```java= public class Application { public static void main(String[] args) { // new 實例化了一個對象 Person person = new Person(); System.out.println(person.name); // Jerry Wang Person person2 = new Person("狂神"); System.out.println(person2.name); // 狂神 } } ``` ```java= public class Person { String name; // 一個類即使什麼都不寫,它也會存在一個方法 // 1. 使用new關鍵字,本質是在調用構造器 // 2. 用來初始化值 // 顯示定義構造器 // 無參構造 public Person(){ // 實例化初始值 this.name = "Jerry Wang"; } // 有參構造: 一旦定義了有參構造,無參構造就必須顯示定義 // 方法重載 public Person(String name) { this.name = name; } } ``` - VS Code構建器快捷方式: Class程式內部,右鍵選擇"Source Action",選擇欲定義參數 *** # 創建對象內存分析 - Code ``` java= package com.oop; import com.oop.demo03.Pet; // 一個項目 應該只存在一個main方法 public class Application { public static void main(String[] args) { Pet dog = new Pet(); dog.name = "旺財"; dog.age = 3; dog.shout(); System.out.println(dog.name); System.out.println(dog.age); Pet cat = new Pet(); } } ``` ``` java= package com.oop.demo03; public class Pet { public String name; public int age; // 無參構造 // 自動生成 //=============== // 方法 public void shout(){ System.out.println("叫了一聲"); } } ``` - 內存分析 ![](https://i.imgur.com/sJa1b8V.png) *** # 簡單小節與對象 1. 類與對象 - 類是一個抽象的模板,對象是一個具體的實例 2. 方法 - 定義、調用 3. 對象的引用 - 八大基本類型 : byte, char, short, int, long, float, double, boolean - 引用類型 - 對象是通過引用來操作的 : 棧 --->堆(地址) 4. 屬性 : 字段Field 、成員變量 - 默認初始化 : - 數字 : 0, 0.0 - char : u0000 - boolean : false - 引用 : null - 修飾符 屬性類型 屬性名 = 屬性值! 5. 對象的創建和使用 - 必須使用new關鍵字創造對象、構造器 - Person Jerry = new Person(); - 對象的屬性 - Jerry.name - 對象的方法 - Jerry.sleep() 6. 類 - 靜態的屬性 ---> 屬性 - 動態的行為 ---> 方法 --- # 封裝詳解 - **該露的露,該藏的藏** - 高內聚 : 內部操作自己完成,不允許外部干涉 - 低耦合 : 僅暴露少量方法給外部使用 - **屬性私有(private), get/set** - VS Code 快捷鍵 : 右鍵 Source Action ---> Generate Getters and Setters - Why 封裝 1. 提高程序安全性,保護數據 2. 隱藏代碼的實現細節 3. 統一接口 4. 系統的可維護性增加了 ```java= public class Application { public static void main(String[] args) { Student s1 = new Student(); s1.setName("Jerry Wang"); System.out.println(s1.getName()); s1.setAge(70); System.out.println(s1.getAge()); } } ``` ```java= // 類 private: 私有 public class Student { // 屬性私有 private String name; // Name private int id; // ID private char sex; // sex private int age; // 提供一些可以操作這個屬性的方法! // 提供一些public的get/set方法 // get 獲取這個數據 public String getName(){ return this.name; } // set 給這個數據設置值 public void setName(String name){ this.name = name; } // 快捷 : 右鍵 Source Action ---> Generate Getters and Setters public int getId() { return id; } public void setId(int id) { this.id = id; } public char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { if (age > 120 || age < 0){ // Illegal this.age = 3; }else{ this.age = age; } } } ``` --- # 什麼是繼承 - 類的進一步抽象 ---> extends - 子類繼承了父類,就會擁有父類的全部方法! - 修飾詞 : public, protected, default, private - **私有(Private)的東西無法被繼承** - VS Code : 右鍵,Show Type Hierarchy,可以顯示當前類的繼承狀況 - 在Java中,**所有的類,都默認直接或者間接繼承Object類** - Java中,類只有單繼承,沒有多繼承 (一個兒子只能有一個爸爸,但一個爸爸可以有多個兒子) - 宣告Final修飾,無法被繼承 ---> 無子類!! --- # Super 詳解 - 利用Super可以調用父類的公開參數與方法 - **By default** : 子類由(無參)建構器初始化時,會先初始化父類,接著在初始化子類。 ## 注意點 : 1. super調用父類的構造方法,必須在構造方法的第一個 2. super必須只能出現在子類的方法或者構造方法中! 3. super和this不能同時調用構造方法! ## Vs this : - 代表的對象不同 - this : 本身調用者這個對象 - super : 代表父類對象的應用 - 前提 - this : 沒有繼承也可以使用 - super : 只能在繼承條件下才可以使用 - 構造方法 - this() : 本類的構造 - super() : 父類的構造 --- # 方法重寫 - **重要!!** 影響後面多態的學習 - 重寫都是方法的重寫,和屬性無關 - VS Code 快捷 : 右鍵Source Action,Override/Implement Methods ## 靜態方法和非靜態方法區別很大 - 靜態方法 : 方法的調用只和左邊,定義的數據類型有關 - 非靜態 : 重寫 - 網友提供 : - static的方法,初始化時,會隨著類產生;非static類,是跟著對象(=的右邊) - static方法不能被重寫 ## 總整 - 需要有繼承關係,且必須是子類重寫父類的方法! 1. 方法名必須相同 2. 參數列表必須相同(若不同則變成方法重載) 3. 修飾符 範圍可以擴大但不能縮小 : public > protected > default > private 4. 拋出的異常 : 範圍可以被縮小,但不能擴大: ClassNotFoundException ---> Exception(大) 5. **重寫,子類和父類的方法必須要一致、但方法體不同!** ## 為什麼需要重寫? - 父類的功能,子類不一定需要,或者不一定滿足! ```java= public class Application { public static void main(String[] args) { A a = new A(); // 父類的引用指向了子類 B b = new A(); a.test(); // A=>test() b.test(); //A=>test() a.test_static(); // A=>test() b.test_static(); // B=>test() } } ``` ```java= public class A extends B{ // Override 重寫 @Override //注解 : 有功能的注釋 public void test() { System.out.println("A=>test()"); } public static void test_static(){ System.out.println("A=>test()"); } } ``` ```java= public class B { public void test(){ System.out.println("B=>test()"); } public static void test_static() { System.out.println("B=>test()"); } } ``` --- # 什麼是多態 - **對象**的實際類型是確定的,但可以**指向的引用類型**就不確定了。 - ***對象能執行哪些方法,主要看對象左邊的類型,和右邊關係不大 !。*** ## 多態注意事項 1. 多態是方法的多態,屬性沒有多態 2. 父類和子類,有聯繫 - 類型轉換異常 : ClassCastException! 3. 存在的條件: 繼承關係、方法需要重寫、父類引用指向子類對象 ! ( Father f1 = new Son(); ) 4. **有些方法不能被重寫** - static 方法屬於類,他不屬於實例 - final 常量 - private 方法 ``` java= public class Application { public static void main(String[] args) { // 一個對象的實際類型是確定的 // new Student(); // new Person(); // 可以指向的引用類型就不確定了: // 父類的引用指向子類 // Student能調用的方法都是自己的或者繼承父類的! Student s1 = new Student(); // Person 父類型,可以指向子類,但是不能調用子類獨有的方法 Person s2 = new Student(); Object s3 = new Student(); s2.run(); // son run, 子類重寫了父類的方法,執行子類的方法 s1.run(); // son run s1.eat(); // son eat // s2.eat(); // Wrong ((Student) s2).eat(); // son eat, 類型強制轉換(高轉低,父轉子) } } ``` ```java= public class Person { public void run(){ System.out.println("run"); } } ``` ```java= public class Student extends Person{ @Override public void run() { System.out.println("son run"); } public void eat() { System.out.println("son eat"); } } ``` --- # instanceof 和類型轉換 - instanceof 要求對應的兩個類要有包含關係,由 = 的左邊決定 - 編譯的結果由實例化(=的右邊)決定 ```java= public class Application { public static void main(String[] args) { // Object => String // Object => Person => Teacher // Object => Person => Student Object object = new Student(); System.out.println(object instanceof Student); // true System.out.println(object instanceof Person); // true System.out.println(object instanceof Object); // true System.out.println(object instanceof Teacher); // false System.out.println(object instanceof String); // false System.out.println("======"); Person person = new Student(); System.out.println(person instanceof Student); // true System.out.println(person instanceof Person); // true System.out.println(person instanceof Object); // true System.out.println(person instanceof Teacher); // false // System.out.println(person instanceof String); // 編譯即報錯 System.out.println("======"); Student student = new Student(); System.out.println(student instanceof Student); // true System.out.println(student instanceof Person); // true System.out.println(student instanceof Object); // true // System.out.println(student instanceof Teacher); // 編譯即報錯 // System.out.println(student instanceof String); // 編譯即報錯 System.out.println("======"); } } ``` - 層級低轉高<**自動完成,合法**>,高轉低<**特別聲明**> - 子類轉換為父類時,可能丟失自己本來的一些方法 ```java= public class Application { public static void main(String[] args) { // 類型之間的轉換: 父 子 // 高 低 Person obj = new Student(); // obj.go(); // 報錯 // 將student這個對象轉換為Student類型,我們就可以使用Student類型的方法了! ((Student) obj).go(); // 子類轉換為父類,可能丟失自己的本來的一些方法 Student student = new Student(); student.go(); Person person = student; // person.go() // 報錯 } } ``` ## 多態段落總整 1. 父類引用指向子類的對象 2. 把子類轉換為父類,**向上轉型 : 無須強制轉型** 3. 把父類轉換為子類,**向下轉型 : 強制轉換,可能丟失方法** 4. 方便方法的調用,減少重複的代碼 ! 簡潔 ! --- # static 關鍵字詳解 - 靜態變量對於類而言,應讓所有對象(實例)共享,使用上直接使用類去調用可解釋這個變量是靜態的。 ``` java= public class Student { private static int age; // 靜態的變量 多線程! private double score; // 非靜態的變量 public static void main(String[] args) { System.out.println(Student.age); // 對於靜態變量的推薦使用方式 // System.out.println(Student.score); // 報錯 Student s1 = new Student(); System.out.println(s1.age); System.out.println(s1.score); // run(); // 報錯 go(); // Student.run(); // 報錯 Student.go(); s1.run(); s1.go(); } public void run(){ } public static void go(){ } } ``` - **靜態方法只執行一次,且先於構造器** ``` java= public class Person { // 2, 可用於賦初值 { System.out.println("匿名代碼塊"); } // 1, 只執行一次 static{ System.out.println("靜態代碼塊"); } // 3 public Person() { System.out.println("構造方法"); } public static void main(String[] args) { Person person = new Person(); // 靜態代碼塊 // 匿名代碼塊 // 構造方法 System.out.println("========="); Person person1 = new Person(); System.out.println("========="); Person person2 = new Person(); // ========= // 匿名代碼塊 // 構造方法 // ========= // 匿名代碼塊 // 構造方法 } } ``` - 靜態導入包 ``` java= // 靜態導入包 import static java.lang.Math.random; public class Test { public static void main(String[] args) { System.out.println(random()); } } ``` --- # 抽象類 - abstract 修飾詞 - 抽象類的子類必須實現(重寫)父類的所有方法,除非子類也是abstract - Java是單繼承,但透過**接口**可以實現多繼承 1. 不能new這個抽象類,只能靠子類去實現他 ( **約束!** ) 2. 抽象類中可以寫普通方法 3. 抽象方法必須在抽象類中 4. 抽象的抽象: **約束** ```java= public abstract class Action { // 約束,有人幫我們實現 // abstract, 抽象方法,只有方法名字,沒有方法的實現! public abstract void doSomething(); public void sayHello(){ System.out.println(); } } ``` ```java= public class A extends Action { @Override public void doSomething() { // TODO Auto-generated method stub } } ``` --- # 接口的定義與實現 ## 普通類、抽象類、接口 - 普通類 : 只有具體實現 - 抽象類 : 具體實現和約束都有 - 接口 : 只有約束 - 自己無法寫方法,專業的約束! - 約束和實現分離 : 面向接口編程 - **接口的本質是契約** - 關鍵字 : interface, implements - [ ] 補充 : [23種設計模式](https://www.bilibili.com/video/BV1mc411h719/?spm_id_from=333.337.search-card.all.click&vd_source=c5074574112ef27dae243d70aa2175b8) ## 接口作用 1. 約束 2. 定義一些方法,讓不同的人實現 (多 ---> 1) 3. 方法都是 public abstract 4. 變量都是靜態常量(public static final) 5. 接口無法被實例化,因為接口中沒有構造方法 6. implements 可以實現多個接口 7. 必須要重寫接口中的方法 ## Code ```java= // 抽象的思維 Java 架構師 // interface 定義的關鍵字,接口都需要有實現類 public interface UserService { // 接口中的定義變量,靜態常量(public static final) // 很少在接口定義常量 int AGE = 99; // 接口中的所有的定義方法其實都是抽象的 public abstract public abstract void run(); void add(String name); void delete(String name); void update(String name); void query(String name); } ``` ```java= public interface TimeService { void timer(); } ``` ``` java= package com.oop.demo09; // 抽象類 : extends, 單繼承 // 類 可以實現接口 implements 接口 // 實現了接口的類,就需要重寫接口中的方法~ // 多繼承,利用接口實現多繼承 public class UserServiceImpl implements UserService, TimeService{ @Override public void add(String name) { // TODO Auto-generated method stub } @Override public void delete(String name) { // TODO Auto-generated method stub } @Override public void query(String name) { // TODO Auto-generated method stub } @Override public void run() { // TODO Auto-generated method stub } @Override public void update(String name) { // TODO Auto-generated method stub } @Override public void timer() { // TODO Auto-generated method stub } } ``` --- # N 種內部類 (補充) ## 成員內部類 - 類中類 ```java= package com.oop; import com.oop.demo10.Outer; public class Application { public static void main(String[] args) { // 外部類實例化 Outer outer = new Outer(); // 透過 "外部類" 實例化 "內部類" Outer.Inner inner = outer.new Inner(); inner.in(); // 這是內部類的方法 inner.getID(); // 10 } } ``` ```java= package com.oop.demo10; public class Outer { private int id = 10; public void out(){ System.out.println("這是外部類的方法"); } public class Inner{ public void in(){ System.out.println("這是內部類的方法"); } // 內部類取得外部類資訊(私有資訊) public void getID(){ System.out.println(id); } } } ``` --- ## 匿名內部類 - 一個 java 文件中可以有多個 class類,但只能有一個是 public class - 接口雖然在前面提到,無法使用new實例化,但可用在匿名內部類重寫方法 ```java= package com.oop.demo10; public class Test { public static void main(String[] args) { // Apple apple = new Apple(); // 匿名初始化類,不用將實例保存到變量中 new Apple().eat(); new UserService() { @Override public void hello() { // TODO Auto-generated method stub } }; } } class Apple{ public void eat(){ System.out.println("1"); } } interface UserService{ void hello(); } ``` --- ## 局部內部類 - 存在於方法中,隨著方法生滅 ``` java= public class Outer { // 方法 public void method(){ // 局部內部類 // 存在於方法中,隨著方法生滅 class Inner{ public void in(){ } } } } ```