# 狂神說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("叫了一聲");
}
}
```
- 內存分析

***
# 簡單小節與對象
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(){
}
}
}
}
```