ch07_oop_fundamentals

小兵学习笔记發表於2024-09-13
  • 第7章 物件導向程式設計(基礎部分)
    • 類與物件
      • 類和物件的區別和聯絡
      • 物件在記憶體中存在形式!
      • 屬性/成員變數/欄位
      • 如何建立物件
      • 如何訪問屬性
    • 成員方法
      • 方法的呼叫機制原理!
      • 成員方法的好處
      • 成員方法的定義
    • 成員方法傳參機制
      • 引用資料型別的傳參機制
      • 成員方法返回型別是引用型別應用例項
    • 方法遞迴呼叫
      • 方法遞迴呼叫
      • 遞迴重要規則
      • 遞迴呼叫應用例項-漢諾塔
      • 遞迴呼叫應用例項-八皇后問題
    • 方法過載(OverLoad)
      • 基本介紹
      • 過載的好處
      • 注意事項和使用細節
    • 可變引數
      • 基本概念
      • 基本語法
      • 注意事項和使用細節
    • 作用域
      • 基本使用
    • 構造方法/構造器
      • 基本介紹
      • 注意事項和使用細節
    • javap的使用
    • 物件建立的流程分析
      • 流程分析!
    • this 關鍵字
      • 深入理解this
      • this 的注意事項和使用細節
      • this 的案例

第7章 物件導向程式設計(基礎部分)

類與物件

類和物件的區別和聯絡

  1. 類是抽象的,概念的,代表一類事物,比如人類,貓類.., 即它是資料型別.
  2. 物件是具體的,實際的,代表一個具體事物, 即是例項.
  3. 類是物件的模板,物件是類的一個個體,對應一個例項

物件在記憶體中存在形式!

字串本質上是一個引用型別,按照jvm的規則會把字串放在方法區的常量池中間。

棧中的是物件引用(物件名),實際上的物件在堆中。

// 建立Person 物件
// p1 是物件名(物件引用)
// new Person() 建立的物件空間(資料) 才是真正的物件
Person p1 = new Person();
// 物件的屬性預設值,遵守陣列規則:

屬性/成員變數/欄位

從概念或叫法上看: 成員變數 = 屬性 = field(欄位) (即成員變數是用來表示屬性的,統一叫屬性)

class Car {
    String name;//屬性, 成員變數, 欄位field
    double price;
    String color;
    String[] master;//屬性可以是基本資料型別,也可以是引用型別(物件,陣列)
}

屬性是類的一個組成部分,一般是基本資料型別, 也可是引用型別(物件,陣列)。比如前面定義貓類的 int age 就是屬性

注意事項和細節說明

  1. 屬性的定義語法同變數,示例:訪問修飾符屬性型別屬性名;
    訪問修飾符: 控制屬性的訪問範圍
    有四種訪問修飾符public, proctected, 預設, private ,後面我會詳細介紹
  2. 屬性如果不賦值,有預設值,規則和陣列一致。

如何建立物件

  1. 先宣告再建立
    Cat cat ; //宣告物件cat
    cat = new Cat(); //建立
  2. 直接建立
    Cat cat = new Cat();

如何訪問屬性

基本語法

物件名.屬性名;

cat.name ;
cat.age;
cat.color;

Person p1=new Person0;
p1.age=10;
p1.name="小明";
Person p2=p1;//把p1賦給了p2,讓p2指向p1
System.out.println(p2.age);

請問:p2.age究竟是多少? 10 並畫出記憶體圖:

核心:引用傳遞傳遞的是地址。

成員方法

在某些情況下,我們要需要定義成員方法(簡稱方法)。

方法的呼叫機制原理!

成員方法的好處

  1. 提高程式碼的複用性
  2. 可以將實現的細節封裝起來,然後供其他使用者來呼叫即可

成員方法的定義

訪問修飾符 返回資料型別 方法名(形參列表..){//方法體
    語句;
    return 返回值;
}
// 如果方法是void,則方法體中可以沒有return 語句,或者只寫return;

訪問修飾符(作用是控制方法使用的範圍)
如果不寫預設訪問,[有四種: public, protected, 預設, private]

方法不能巢狀定義!

成員方法傳參機制

基本資料型別,傳遞的是值(值複製),形參的任何改變不影響實參!

引用資料型別的傳參機制

引用型別傳遞的是地址(傳遞也是值,但是值是地址),可以透過形參影響實參!

棧的值是地址,改的時候修改的是對應堆中的值。

例子:

public class MethodParameter02 { 
    //編寫一個main方法
    public static void main(String[] args) {
        //測試
        B b = new B();
        // int[] arr = {1, 2, 3};
        // b.test100(arr);//呼叫方法
        // System.out.println(" main的 arr陣列 ");
        // //遍歷陣列
        // for(int i = 0; i < arr.length; i++) {
        //     System.out.print(arr[i] + "\t");
        // }
        // System.out.println();

        //測試
        Person p = new Person();
        p.name = "jack";
        p.age = 10;
        b.test200(p);
        //測試題, 如果 test200 執行的是 p = null ,下面的結果是 10
        //測試題, 如果 test200 執行的是 p = new Person();..., 下面輸出的是10
        System.out.println("main 的p.age=" + p.age);//10000 
    }
}
class Person {
    String name;
    int age; 
}
class B {
    public void test200(Person p) {
        //p.age = 10000; //修改物件屬性
        //思考
        p = new Person();
        p.name = "tom";
        p.age = 99;
        //思考
        //p = null; 
    }

    //B類中編寫一個方法test100,
    //可以接收一個陣列,在方法中修改該陣列,看看原來的陣列是否變化
    public void test100(int[] arr) {
        arr[0] = 200;//修改元素
        //遍歷陣列
        System.out.println(" test100的 arr陣列 ");
        for(int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
    }
}

B 類中編寫一個方法test100,可以接收一個陣列,在方法中修改該陣列,看看原來的陣列是否變化?會變化

B 類中編寫一個方法test200,可以接收一個Person(age,sal)物件,在方法中修改該物件屬性,看看原來的物件是否變化?會變化.

p=null 和p = new Person(); 對應示意圖

這裡再對class B中的p進行修改,由於在Class B中重新new 了一個p,因此p的指標發生了改變,指向堆中的一個新空間,因此這時修改p的引數,不對main中物件造成影響。

成員方法返回型別是引用型別應用例項

透過這種方式可以編寫方法複製物件。

public class MethodExercise02 { 

    //編寫一個main方法
    public static void main(String[] args) {

        Person p = new Person();
        p.name = "milan";
        p.age = 100;
        //建立tools
        MyTools tools = new MyTools();
        Person p2 = tools.copyPerson(p);

        //到此 p 和 p2是Person物件,但是是兩個獨立的物件,屬性相同
        System.out.println("p的屬性 age=" + p.age  + " 名字=" + p.name);
        System.out.println("p2的屬性 age=" + p2.age  + " 名字=" + p2.name);
        //這裡老師提示: 可以同 物件比較看看是否為同一個物件
        System.out.println(p == p2);//false


    }
}

class Person {
    String name;
    int age;
}

class MyTools {
    //編寫一個方法copyPerson,可以複製一個Person物件,返回複製的物件。克隆物件, 
    //注意要求得到新物件和原來的物件是兩個獨立的物件,只是他們的屬性相同
    //
    //編寫方法的思路
    //1. 方法的返回型別 Person
    //2. 方法的名字 copyPerson
    //3. 方法的形參 (Person p)
    //4. 方法體, 建立一個新物件,並複製屬性,返回即可

    public Person copyPerson(Person p) {
        //建立一個新的物件
        Person p2 = new Person();
        p2.name = p.name; //把原來物件的名字賦給p2.name
        p2.age = p.age; //把原來物件的年齡賦給p2.age
        return p2;
    }
}

方法遞迴呼叫

方法遞迴呼叫

列舉兩個小案例,來幫助大家理解遞迴呼叫機制

  1. 列印問題
  2. 階乘問題
public class Recursion01 { 

    //編寫一個main方法
    public static void main(String[] args) {

        T t1 = new T();
        t1.test(4);//輸出什麼? n=2 n=3 n=4
        int res = t1.factorial(5); 
        System.out.println("5的階乘 res =" + res);
    }
}

class T {
    //分析
    public void test(int n) {
        if (n > 2) {
            test(n - 1);
        } 
        System.out.println("n=" + n);
    }

    //factorial 階乘
    public int factorial(int n) {
        if (n == 1) {
            return 1;
        } else {
            return factorial(n - 1) * n;
        }
    }
}

遞迴重要規則

1.執行一個方法時,就建立一個新的受保護的獨立空間(錢空間)

2.方法的區域性變數是獨立的,不會相互影響,比如n變數

3.如果方法中使用的是引用型別變數(比如陣列,物件),就會共享該引
用型別的資料。
4.遞迴必須向退出遞迴的條件逼近,否則就是無限遞迴,出現StackOverflowError
5.當一個方法執行完畢,或者遇到return,就會返回,遵守誰呼叫,就
將結果返回給誰,同時當方法執行完畢或者返回時,該方法也就執行完畢。

遞迴呼叫應用例項-漢諾塔

public class HanoiTower { 

    //編寫一個main方法
    public static void main(String[] args) {

        Tower tower = new Tower();
        tower.move(64, 'A', 'B', 'C');
    }
}

class Tower {

    //方法
    //num 表示要移動的個數, a, b, c 分別表示A塔,B 塔, C 塔
    public void move(int num , char a, char b ,char c) {
        //如果只有一個盤 num = 1
        if(num == 1) {
            System.out.println(a + "->" + c);
        } else {
            //如果有多個盤,可以看成兩個 , 最下面的和上面的所有盤(num-1)
            //(1)先移動上面所有的盤到 b, 藉助 c
            move(num - 1 , a, c, b);
            //(2)把最下面的這個盤,移動到 c
            System.out.println(a + "->" + c);
            //(3)再把 b塔的所有盤,移動到c ,藉助a
            move(num - 1, b, a, c);
        }
    }
}

遞迴呼叫應用例項-八皇后問題

八皇后問題,是一個古老而著名的問題,是回溯演算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848 年提出:在8×8 格的國際象棋上擺放八個皇后,使其不能互相攻擊,即:任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。

  1. 第一個皇后先放第一行第一列

  2. 第二個皇后放在第二行第一列、然後判斷是否OK,如果不OK,繼續放在第二列、第三列、依次把所有列都放完,找到一個合適

  3. 繼續第三個皇后,還是第一列、第二列...….直到第8個皇后也能放在一個不衝突的位置,算是找到了一個正確解

  4. 當得到一個正確解時,在棧回退到上一個棧時,就會開始回溯,即將第一個皇后,放到第一列的所有正確解,全部得到.

  5. 然後回頭繼續第一個皇后放第二列,後面繼續迴圈執行1,2,3.4的步驟

  6. 說明:理論上應該建立一個二維陣列來表示棋盤,但是實際上可以透過演算法,用一個一維陣列即可解決問題. arr[8]={0,4,7,5.2, 6,1.3)//對應arr下標表示第幾行,即第幾個皇后,arr[i]= val , val表示第i+1個皇后,放在第i+1行的第val+1列

方法過載(OverLoad)

基本介紹

java 中允許同一個類中,多個同名方法的存在,但要求形參列表不一致!

過載的好處

  1. 減輕了起名的麻煩
  2. 減輕了記名的麻煩

注意事項和使用細節

1)方法名: 必須相同

2)形參列表: 必須不同(形參型別或個數或順序,至少有一樣不同,引數名無要求)

3)返回型別: 無要求

可變引數

基本概念

java 允許將同一個類中多個同名同功能但引數個數不同的方法,封裝成一個方法。就可以透過可變引數實現

基本語法

訪問修飾符返回型別方法名(資料型別... 形參名) {
}

看一個案例類HspMethod,方法sum。

public class VarParameter01 { 
    //編寫一個main方法
    public static void main(String[] args) {
        HspMethod m = new HspMethod();
        System.out.println(m.sum(1, 5, 100)); //106
        System.out.println(m.sum(1,19)); //20
    }
}

class HspMethod {
    //可以計算 2個數的和,3個數的和 , 4. 5, 。。
    //可以使用方法過載
    // public int sum(int n1, int n2) {//2個數的和
    //     return n1 + n2;
    // }
    // public int sum(int n1, int n2, int n3) {//3個數的和
    //     return n1 + n2 + n3;
    // }
    // public int sum(int n1, int n2, int n3, int n4) {//4個數的和
    //     return n1 + n2 + n3 + n4;
    // }
    //.....
    //上面的三個方法名稱相同,功能相同, 引數個數不同-> 使用可變引數最佳化
    //老韓解讀
    //1. int... 表示接受的是可變引數,型別是int ,即可以接收多個int(0-多) 
    //2. 使用可變引數時,可以當做陣列來使用 即 nums 可以當做陣列
    //3. 遍歷 nums 求和即可
    public int sum(int... nums) {
        //System.out.println("接收的引數個數=" + nums.length);
        int res = 0;
        for(int i = 0; i < nums.length; i++) {
            res += nums[i];
        }
        return res;
    }
}

注意事項和使用細節

1)可變引數的實參可以為0個或任意多個。

2)可變引數的實參可以為陣列。

3)可變引數的本質就是陣列。

4)可變引數可以和普通型別的引數一起放在形參列表,但必須保證可變引數在最後

5)一個形參列表中只能出現一個可變引數

public void f3(int... nums1, double... nums2) (X錯誤)

作用域

基本使用

1.在java程式設計中,主要的變數就是屬性(成員變數)區域性變數

2.我們說的區域性變數一般是指在成員方法中定義的變數

3.java中作用域的分類

全域性變數:也就是屬性,作用域為整個類體 (Cat類:cry eat等方法使用屬性)

區域性變數:也就是除了屬性之外的其他變數,作用域為定義它的程式碼塊中!

4.全域性變數(屬性)可以不賦值,直接使用,因為有預設值,區域性變數必須賦值後,
才能使用,因為沒有預設值。

public class VarScope { 
    //編寫一個main方法
    public static void main(String[] args) {
    }
}
class Cat {
    //全域性變數:也就是屬性,作用域為整個類體 Cat類:cry eat 等方法使用屬性
    //屬性在定義時,可以直接賦值
    int age = 10; //指定的值是 10

    //全域性變數(屬性)可以不賦值,直接使用,因為有預設值,
    double weight;  //預設值是0.0

    public void hi() {
        //區域性變數必須賦值後,才能使用,因為沒有預設值
        int num = 1;
        String address = "北京的貓";
        System.out.println("num=" + num);
        System.out.println("address=" + address);
        System.out.println("weight=" + weight);//屬性
    }
    public void cry() {
        //1. 區域性變數一般是指在成員方法中定義的變數
        //2. n 和  name 就是區域性變數
        //3. n 和 name的作用域在 cry方法中
        int n = 10;
        String name = "jack";
        System.out.println("在cry中使用屬性 age=" + age);
    }
    public void eat() {
        System.out.println("在eat中使用屬性 age=" + age);
        //System.out.println("在eat中使用 cry的變數 name=" + name);//錯誤
    }
}

1.屬性和區域性變數可以重名,訪問時遵循就近原則。

2.在同一個作用域中,比如在同一個成員方法中,兩個區域性變數,不能重名。

3.屬性生命週期較長,伴隨著物件的建立而建立,伴隨著物件的銷燬而銷燬。區域性變
量,生命週期較短,伴隨著它的程式碼塊的執行而建立,伴隨著程式碼塊的結束而銷燬。

4.作用域範圍不同

全域性變數/屬性:可以被本類使用,或其他類使用(透過物件呼叫)

區域性變數:只能在本類中對應的方法中使用

5.修飾符不同

全域性變數/屬性可以加修飾符

區域性變數不可以加修飾符

構造方法/構造器

[修飾符] 方法名(形參列表){
    方法體;
}
  1. 構造器的修飾符可以預設, 也可以是public protected private
  2. 構造器沒有返回值
  3. 方法名和類名字必須一樣
  4. 引數列表和成員方法一樣的規則
  5. 構造器的呼叫, 由系統完成

基本介紹

構造方法又叫構造器(constructor),是類的一種特殊的方法,它的主要作用是完成對新物件的初始化。它有幾個特點:

  1. 方法名和類名相同
  2. 沒有返回值
  3. 在建立物件時,系統會自動的呼叫該類的構造器完成物件的初始化。

注意事項和使用細節

1.一個類可以定義多個不同的構造器,即構造器過載

比如:我們可以再給Person類定義一個構造器,用來建立物件的時候,只指定人名,不需要指定年齡。

2.構造器名和類名要相同

3.構造器沒有返回值

4.構造器是完成物件的初始化,並不是建立物件

5.在建立物件時,系統自動的呼叫該類的構造方法

6.如果程式設計師沒有定義構造器,系統會自動給類生成一個預設無參構造器(也
叫預設構造器). 比如Dog(){}, 使用javap指令反編譯看看

可以使用javap Dog.class 檢視

7.一旦定義了自己的構造器,預設的構造器就覆蓋了,就不能再使用預設的無
參構造器,除非顯式的定義一下,即:Dog(){}

javap的使用

javap是JDK提供的一個命令列工具,javap能對給定的class檔案提供的位元組程式碼進行反編譯。 透過它,可以對照原始碼和位元組碼,從而瞭解很多編譯器內部的工作。

使用格式

javap <options> <classes>

常用

javap -c -v 類名
  -help  --help  -?        輸出此用法訊息
  -version                 版本資訊
  -v  -verbose             輸出附加資訊
  -l                       輸出行號和本地變數表
  -public                  僅顯示公共類和成員
  -protected               顯示受保護的/公共類和成員
  -package                 顯示程式包/受保護的/公共類
                           和成員 (預設)
  -p  -private             顯示所有類和成員
  -c                       對程式碼進行反彙編
  -s                       輸出內部型別簽名
  -sysinfo                 顯示正在處理的類的
                           系統資訊 (路徑, 大小, 日期, MD5 雜湊)
  -constants               顯示最終常量
  -classpath <path>        指定查詢使用者類檔案的位置
  -cp <path>               指定查詢使用者類檔案的位置
  -bootclasspath <path>    覆蓋引導類檔案的位置

物件建立的流程分析

class Person{//類Person
    int age=90;
    String name;
    Person(String n,int a){//構造器
    name=n;//給屬性賦值
    age=a;//..
    }
}
Person p=new Person("TIMERRING",20);

流程分析!

1.載入Person類資訊(Person.class) 到方法區,只會載入一次

2.在堆中分配空間(地址)

3.完成物件初始化[3.1預設初始化 age=0 name=null 3.2顯式初始化age=90,name=null,3.3構造器的初始化 age =20, name=TIMERRING]

4.在物件在堆中的地址,返回給p(p是物件名,也可以理解成是物件的引用)

this 關鍵字

可以正常執行,但是是否可以將建構函式的形參改為屬性值呢?可以用this。

public class This01 { 

    //編寫一個main方法
    public static void main(String[] args) {

        Dog dog1 = new Dog("大壯", 3);
        System.out.println("dog1的hashcode=" + dog1.hashCode());
        //dog1呼叫了 info()方法
        dog1.info(); 

        System.out.println("============");
        Dog dog2 = new Dog("大黃", 2);
        System.out.println("dog2的hashcode=" + dog2.hashCode());
        dog2.info();
    }
}

class Dog{ //類

    String name;
    int age;
    // public Dog(String dName, int  dAge){//構造器
    //     name = dName;
    //     age = dAge;
    // }
    //如果我們構造器的形參,能夠直接寫成屬性名,就更好了
    //但是出現了一個問題,根據變數的作用域原則
    //構造器的 name 是區域性變數,而不是屬性
    //構造器的 age  是區域性變數,而不是屬性
    //==> 引出this關鍵字來解決
    public Dog(String name, int  age){//構造器
        //this.name 就是當前物件的屬性name
        this.name = name;
        //this.age 就是當前物件的屬性age
        this.age = age;
        System.out.println("this.hashCode=" + this.hashCode());
    }

    public void info(){//成員方法,輸出屬性x資訊
        System.out.println("this.hashCode=" + this.hashCode());
        System.out.println(name + "\t" + age + "\t");
    }
}

java虛擬機器會給每個物件分配this, 代表當前物件。

this.name 就是當前物件屬性name。

深入理解this

隱藏的this指向自己的堆地址。

this 的注意事項和使用細節

  1. this 關鍵字可以用來訪問本類的屬性、方法、構造器

  2. this 用於區分當前類的屬性和區域性變數

  3. 訪問成員方法的語法:this.方法名(引數列表);

  4. 訪問構造器語法:this(引數列表); 注意只能在構造器中使用(即只能在構造器中訪問另外一個構造器, 必須放在第一條語句)

    public class ThisDetail { 
    
        //編寫一個main方法
        public static void main(String[] args) {
    
            // T t1 = new T();
            // t1.f2();
            T t2 = new T();
            t2.f3();
    
        }
    }
    
    class T {
    
        String name = "jack";
        int num = 100;
    
        /*
        細節: 訪問構造器語法:this(引數列表); 
        注意只能在構造器中使用(即只能在構造器中訪問另外一個構造器)
        注意: 訪問構造器語法:this(引數列表); 必須放置第一條語句!!!
         */
        public T() {
            //這裡去訪問 T(String name, int age) 構造器
            this("jack", 100);
            System.out.println("T() 構造器");
        }
    
        public T(String name, int age) {
            System.out.println("T(String name, int age) 構造器");
        }
    
        //this關鍵字可以用來訪問本類的屬性
        public void f3() {
            String name = "smith";
            //傳統方式(按照就近原則,有區域性變數先訪問區域性變數)
            System.out.println("name=" + name + " num=" + num);//smith  100
            //也可以使用this訪問屬性(準確地就訪問屬性)
            System.out.println("name=" + this.name + " num=" + this.num);//jack 100
        }
        //細節: 訪問成員方法的語法:this.方法名(引數列表);
        public void f1() {
    
            System.out.println("f1() 方法..");
        }
    
        public void f2() {
            System.out.println("f2() 方法..");
            //呼叫本類的 f1
            //第一種方式
            f1();
            //第二種方式
            this.f1();
        }
    
    }
    
  5. this 不能在類定義的外部使用,只能在類定義的方法中使用

this 的案例

public class TestPerson { 

    //編寫一個main方法
    public static void main(String[] args) {

        Person p1 = new Person("mary", 20);
        Person p2 = new Person("mary", 20);
        System.out.println("p1和p2比較的結果=" + p1.compareTo(p2));
    }
}

/*
定義Person類,裡面有name、age屬性,並提供compareTo比較方法,
用於判斷是否和另一個人相等,提供測試類TestPerson用於測試, 
名字和年齡完全一樣,就返回true, 否則返回false

 */
class Person {
    String name;
    int age;
    //構造器
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //compareTo比較方法
    public boolean compareTo(Person p) {
        return this.name.equals(p.name) && this.age == p.age;
    }
}