物件導向

Chengkai730發表於2024-09-04

介紹

物件導向三大特徵:封裝、繼承、多型。

類裡面能寫哪些東西:

public class 類名{
    1、成員變數(代表屬性,一般是名詞)
    2、成員方法(代表行為,一般是動詞)
    3、構造器
    4、程式碼塊
    5、內部類
}

透過類得到物件:

類名 物件名 = new 類名();

使用物件:

物件名.成員變數;    // 訪問屬性
物件名.方法名;      // 訪問行為

定義一個類:

public class Phone {
    // 屬性(成員變數)
    String brand;
    double price;

    // 行為(方法)
    public void call() {
        System.out.println("手機在打電話");
    }

    public void playGame() {
        System.out.println("手機在玩遊戲");
    }
}

使用這個類:

public class PhoneTest {
    public static void main(String[] args) {
        // 建立手機物件
        Phone p = new Phone();
        // 給手機賦值
        p.brand = "華為";
        p.price = 6999.9;
        // 獲取手機物件中的值
        System.out.println(p.brand);
        System.out.println(p.price);
        // 呼叫手機的方法
        p.call();
        p.playGame();

        Phone p2 = new Phone();
        p2.brand = "小米";
        p2.price = 1999.9;
        System.out.println(p2.brand);
        System.out.println(p2.price);
        p2.call();
        p2.playGame();
    }
}

執行結果:

華為
6999.9
手機在打電話
手機在玩遊戲
小米
1999.9
手機在打電話
手機在玩遊戲

用來描述某一個事物的類稱為 Javabean 類,在 Javabean 類中,是不寫 main() 方法的。

帶 main() 方法的類,稱為測試類。

可以在測試類中建立 Javabean 類的物件並賦值呼叫。

類名採用大駝峰命名規則。

一個 Java 原始檔可以寫多個類,但是隻能有一個 public 類,且 public 類的類名必須和檔名相同。實際開發中,一般一個原始檔只寫一個類。

public是一個訪問修飾符(access modifier),訪問修飾符用於控制程式的其他部分對這段程式碼的訪問級別。

成員變數的完整定義格式為:

修飾符 資料型別 變數名稱 = 初始值;

但是一般都不寫初始值,因為都有預設值。

預設值:

基本資料型別:

  • byte,short,int,long:0

  • float,double:0.0

  • char:空格

  • boolean:false

引用型別:

  • String,類,介面,陣列:null

程式示例:

Javabean 類:

public class Default {
    byte b;
    short s;
    int i;
    long L;
    char c;
    boolean boo;
    float f;
    double d;
    String st;
}

測試類:

public class DefaultTest {
    public static void main(String[] args) {
        Default test = new Default();  // 如果此處寫 Default test; 則會報錯 java: 可能尚未初始化變數test
        System.out.println(test.b);
        System.out.println(test.s);
        System.out.println(test.i);
        System.out.println(test.L);
        System.out.println(test.c);
        System.out.println(test.boo);
        System.out.println(test.f);
        System.out.println(test.d);
        System.out.println(test.st);
    }
}

執行結果:

0
0
0
0
 
false
0.0
0.0
null

private 是一個修飾符,可以修飾成員,例如成員變數和成員方法等,用 private 修飾的成員,只能在本類中訪問。

public 也是一個修飾符,也可以修飾成員,例如成員變數和成員方法等,用 public 修飾的成員,可以在所有的類中訪問。

程式示例:

Javabean 類:

public class Girlfriend {
    String name;
    String sex;
    private int age;

    public void setAge(int a) {
        if (a < 18 || a > 50)
            System.out.println("非法資料。");
        else age = a;
    }

    public int getAge() {
        return age;
    }
}

測試類:

public class GirlfriendTest {
    public static void main(String[] args) {
        Girlfriend g1 = new Girlfriend();
        g1.sex = "女";
        g1.name = "小團團";
        g1.setAge(20);
        System.out.println(g1.getAge());
        System.out.println(g1.sex);
        System.out.println(g1.name);
    }
}

執行結果:

20
女
小團團

用 public 或 private 修飾成員變數時不影響它的預設值。

private 可以防止給成員變數一個不恰當的值。

程式示例:

Javabean 類:

public class GF {
  // 成員變數
  private int age;
  private String name;
  private String gender;

  // getter 和 setter
  public void setAge(int age) {
    if (age < 18 || age > 30) {
      System.out.println("年齡不合適");
    } else {
      this.age = age;
    }
  }

  public int getAge() {
    return age;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setGender(String gender) {
    this.gender = gender;
  }

  public String getGender() {
    return gender;
  }

  // 成員方法
  public void playGame() {
    System.out.println("GF is playing game.");
  }

  public void eat() {
    System.out.println("GF is eating.");
  }
}

測試類:

public class GFtest {
  public static void main(String[] args) {
    GF fg = new GF();

    fg.setName("Hello");
    fg.setAge(20);
    fg.setGender("nv");

    fg.eat();
    fg.playGame();

    System.out.println(fg.getAge());
    System.out.println(fg.getGender());
    System.out.println(fg.getName());
  }
}

執行結果:

GF is eating.
GF is playing game.
20
nv
Hello

定義在方法裡面的變數叫做區域性變數(包括定義在方法頭的形參變數),定義在方法外面類裡面的變數叫做成員變數。遵循就近原則,如果區域性變數和成員變數同名,則在方法內區域性變數覆蓋成員變數。如果不想覆蓋,即在方法中使用同名的成員變數,需要在成員變數前面加關鍵字 this。

this 的本質:代表方法呼叫者的地址。

程式示例 1:

Javabean 類:

public class Girlfriend {
    private int age;        // 成員變數

    public int getAge() {
        int age = 10;       // 區域性變數
        return age;         // 返回區域性變數
    }

    public int getAge1() {
        int age = 10;       // 區域性變數
        return this.age;    // 返回成員變數
    }
}

測試類:

public class GirlfriendTest {
    public static void main(String[] args) {
        Girlfriend g1 = new Girlfriend();
        System.out.println(g1.getAge());        // 10
        System.out.println(g1.getAge1());       // 0,0 是預設值
    }
}

程式示例 2:

Javabean 類:

public class Girlfriend {
    private String name;
    private int age;

    public void setName(String name) {
        name = name;
    }

    public void setName1(String name) {
        this.name = name;
    }

    public void setAge(int a) {
        age = a;
    }

    public String getName() {
        String name = "小團團";
        return name;
    }

    public String getName1() {
        String name = "小張";
        return this.name;
    }

    public int getAge() {
        int age = 10;
        return age;
    }
}

測試類:

public class GirlfriendTest {
    public static void main(String[] args) {
        Girlfriend g1 = new Girlfriend();
        g1.setName("小明");
        g1.setName1("小李");
        g1.setAge(20);
        System.out.println(g1.getAge());        // 10
        System.out.println(g1.getName());       // 小團團
        System.out.println(g1.getName1());      // 小李
    }
}

如果不發生成員變數和區域性變數同名的情況,this 寫不寫都一樣。

程式示例:

Javabean 類:

public class Girlfriend {
    private int age;

    public int getAge() {
        return age;
    }

    public int getAge1() {
        return this.age;
    }
}

測試類:

public class GirlfriendTest {
    public static void main(String[] args) {
        Girlfriend g1 = new Girlfriend();
        System.out.println(g1.getAge());        // 0
        System.out.println(g1.getAge1());       // 0
    }
}

構造方法

構造方法也叫構造器或建構函式。

作用:在建立物件時,給成員變數進行初始化(即賦值)。

構造方法的格式:

public class Student {
    // 構造方法
    修飾符 類名(引數) {
        方法體;
    }
}

注意:

  • 方法名必須和類名完全相同。

  • 沒有返回值型別,連 void 也不能有。

  • 沒有返回值,不能由 return 語句帶回結果資料,即使是 return; 也不能有。

使用空參構造方法時,成員變數被初始化為預設值。空參構造方法的方法體一般都是空著,什麼都不寫。

可以自定義帶參構造方法。在方法體內,可以給成員變數賦值。

構造方法在建立物件時由虛擬機器呼叫,不能手動呼叫構造方法。

每建立一次物件,就會呼叫一次構造方法。

如果沒有自定義的構造方法,那麼虛擬機器會自動新增一個空參構造方法。

如果自定義了有參構造,那麼虛擬機器將不再提供預設的無參構造,在建立物件時,將不能再使用無參構造,如果還需要使用無參構造,則需要我們自己手動書寫無參構造。

因此,建議如果寫了自定義的有參構造,那麼不管是否會用到無參構造,都再手動書寫一個無參構造。

帶參構造和無參構造是構造方法的過載。

帶參構造一定是帶全部引數.

如果在 Javabean 類中,定義的類只有有參構造,沒有無參構造,在測試類中,定義類的物件時,不傳遞引數,想要呼叫無參構造,這時會報錯,因為在 Javabean 類中,已經沒有無參構造了,如果還需要無參構造,就需要自己去寫無參構造了。

程式示例:

Javabean 類:

public class Girlfriend {
    private int age;
    private String name;

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public String getname() {
        return name;
    }

    // 空參構造,虛擬機器自動新增的構造方法就和這個長得一樣。
    public Girlfriend() {

    }

    // 帶參構造
    public Girlfriend(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

測試類:

public class GirlfriendTest {
    public static void main(String[] args) {
        Girlfriend g1 = new Girlfriend();
        System.out.println(g1.getAge());        // 0
        System.out.println(g1.getname());       // null
        Girlfriend g2 = new Girlfriend("小張", 23);
        System.out.println(g2.getAge());        // 23
        System.out.println(g2.getname());       // 小張
        g2.setAge(25);                          // 進行修改
        g2.setName("小王");
        System.out.println(g2.getAge());        // 25
        System.out.println(g2.getname());       // 小王
    }
}

標準的 Javabean 類

類名採用大駝峰。

所有成員變數都用 private 修飾。

提供至少兩個構造方法:無參構造方法和帶全部引數的構造方法。

成員方法:提供對應到每一個成員變數的 setXXX() / getXXX()。如果有其他行為,也需要寫上。

快捷鍵:alt + insert 或者 alt + fn + insert

外掛 PTG 可以 1 秒生成標準 Javabean,快捷鍵:ctrl + shift + ,

物件記憶體圖

物件是 new 出來的,物件中存放的是地址。

程式示例:

Javabean 類:

public class Phone {
    // 屬性(成員變數)
    String brand;
    double price;

    // 行為(方法)
    public void call() {
        System.out.println("手機在打電話");
    }

    public void playGame() {
        System.out.println("手機在玩遊戲");
    }
}

測試類:

public class PhoneTest {
    public static void main(String[] args) {
        // 建立手機物件
        Phone p = new Phone();
        System.out.println(p);    // classes.Phone@776ec8df
    }
}

new 一個物件要發生的事情至少包括以下七個步驟:

Student s = new Student();    // new 一個物件
  1. 載入 Student.class 位元組碼檔案。因為用到了這個類,所以要載入這個類的位元組碼檔案

  2. 申明等號左側的區域性變數

  3. 在堆記憶體中開闢一個空間

  4. 預設初始化

  5. 顯示初始化(在類的定義中給定初始值即為顯式初始化)

  6. 構造方法初始化(在類中定義了有參構造方法時執行這一步)

  7. 將堆記憶體中的地址值賦值給左邊的區域性變數

可以讓兩個變數指向同一個物件:

Javabean 類:

public class Student {
    String name;
}

測試類:

public class StudentTest {
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.name = "小明";
        Student s2 = s1;
        System.out.println(s1.name + "    " + s2.name);         // 小明    小明
        s2.name = "小張";
        System.out.println(s1.name + "    " + s2.name);         // 小張    小張
        s1 = null;
        // System.out.println(s1.name);  // Exception in thread "main" java.lang.NullPointerException: Cannot read field "name" because "s1" is null
        System.out.println(s2.name);                            // 小張
    }
}

一個物件的記憶體圖的分析過程:

下圖的程式碼中,首先是將 TestStudent 類的位元組碼檔案載入到方法區中,並將 main() 方法在其中進行臨時儲存。

接下來,虛擬機器會自動呼叫程式的主入口 main() 方法,於是,main() 方法被載入到棧裡面。

然後開始執行 main() 方法裡面的程式碼。第一句是 Student s = new Student();,建立一個 Student 類的物件 s

建立這個物件時,遵循上面所說的建立一個物件要經歷的 7 個步驟。

第一步是將類 Student 的 class 檔案載入到方法區,且這個空間內有著這個類的全部資訊,比如所有的成員變數 nameage,所有的成員方法 study() 等。

第二步是申明區域性變數,也就是建立物件這條語句 Student s = new Student(); 左邊的程式碼 Student s,在 main() 方法中就開闢了一個空間,這個空間的名字就叫做 s。這個空間可以儲存 Student 這個類的物件的地址值。

第三步,在堆記憶體中開闢一塊空間,也就是建立物件這條語句 Student s = new Student(); 右邊的程式碼 new Student()。堆記憶體裡面的空間都是有地址值的,假設這塊空間的地址值為 001,這塊空間裡面會把 Student 這個類的所有的成員變數都複製一份放過來(從方法區中複製,因為這些資訊儲存在方法區中)。除此之外 001 這塊空間還儲存有 Student 這個類的所有成員方法的地址,這個地址是為了以後用物件呼叫方法的時候能找到對應的方法,等於說並沒有複製成員方法,成員方法還是在方法區中,只是複製了成員變數到堆記憶體中。此時這個地址為 001 的空間就是我們所說的物件。但是這個物件現在還沒有建立完畢。因為此時的成員變數 nameage 都還沒有值。賦值操作就是接下來的 4,5,6 三個步驟。

第四步,首先是預設初始化,name 預設初始化為 nullage 預設初始化為 0

第五步,是顯示初始化,如果 Student 類的定義中,成員變數時直接給定了值,即 String name = "zhangsan";int age = 10;,那麼這就叫做顯示初始化,此時上一步裡面預設初始化的 null0 在這一步中就會被 zhangsan10 代替,但是這裡 Student 類並沒有寫這樣的顯示初始化的程式碼,那麼顯式初始化可以忽略。

第六步,是構造方法初始化,在建立例項 s 時,即 Student s = new Student(); 這條語句中,小括號中什麼都沒寫,就表示現在呼叫的是空參構造。而空參構造是沒有方法體的,所以空參構造是可以忽略的。但是如果此時用的是有參構造,那麼在構造方法初始化這一步,就會有值代替預設初始化中的 null0。從此也可以看出,構造方法只是建立物件的多個步驟中的一個步驟而已。

第七步,即把堆記憶體中的地址賦值給左邊的區域性變數。即把地址 001 透過等號運算子即賦值運算子賦值給了左邊的變數 s。此時 s 這個變數的空間裡面就會儲存地址值 001。於是變數 s 也可以透過地址值 001 找到堆記憶體中的空間。

至此,建立物件的這條語句,即 Student s = new Student();,就執行完了。

如果直接列印 s,就是列印 s 中記錄的地址值 001

s.name 就表示 001 這個記憶體空間裡面的 name,就找到了堆記憶體裡面的 name

如果執行語句 s.name = "aqiang";,則改變的也是堆記憶體裡面的 name

語句 s.study(); 就是找 001 這個空間裡面的 study(),但是找到的是這個成員方法的地址,再用這個地址,找到了方法區裡面的成員方法 study()。此時 study() 方法就會被載入進棧。study() 方法執行完畢後,就會出棧。此時整個 main() 方法就執行完畢了,於是 main() 方法也出棧。既然 main() 方法都出棧了,那麼 main() 裡面的變數自然也就沒有了。於是 s 也就沒有了,於是也就沒有變數再使用堆記憶體中的這塊原本地址值為 001 的這一塊空間了。專業點說就是沒有變數指向這塊空間了,那麼這塊空間也會消失。

兩個物件的記憶體圖的分析過程:

建立第一個物件和第二個物件的過程和上述分析一個物件的記憶體圖的過程是一樣的。

執行 Student s2 = new Student(); 語句時,Student.class 檔案不需要再載入一次,因為已經存在於方法區中了,直接用就可以了。

相關文章