Object

有空發表於2024-11-13

Object 類是 Java 中的頂級父類, 所有的類都直接或間接繼承於 Object 類.

Object 類中的方法可以被所有子類訪問.

Object 類沒有成員變數, 所以只有無參構造方法.

Java 中, 子類的共性才會往父類中去抽取, 然而不可能有一個屬性是所有類的共性, 所以在 Object 這個類中, 是沒有成員變數的.

任意一個類的構造方法, 在第一行, 都有一個隱藏的 super();, 預設訪問父類的無參構造, 那為什麼是預設訪問無參構造而不是有參構造呢? 因為在頂級父類 Object 類中, 只有無參構造.

Object
圖1

Object 類一共有 11 個成員方法. 其中有三個最常見:

Object
圖2

程式示例:

public class Demo1 {
    public static void main(String[] args) {
        /*
            public string toString() 返回物件的字串表示形式
            public boolean equals(Object obj) 比較兩個物件是否相等
            protected object clone(int a) 物件克隆
        */
        // 1.toString 返回物件的字串表示形式
        Object obj = new Object();
        String str1 = obj.toString();
        System.out.println(str1);  // java.lang.Object@119d7047
        // @ 前面是包名加類名, @ 是一個固定格式, @ 後面部分表示地址值
        // 對於自定義的類的物件, 也是同樣的規則:
        Student stu = new Student();
        String str2 = stu.toString();
        System.out.println(str2);  //  Object_demo.Student@4eec7777

        // 直接列印物件, 和呼叫 toString 方法的效果是一樣的:
        System.out.println(str1);  // java.lang.Object@119d7047
        System.out.println(str2);  //  Object_demo.Student@4eec7777

        // 細節:
        // System: 類名
        // out: 靜態變數
        // System.out: 獲取列印的物件
        // println(): 方法
        // 引數: 表示列印的內容
        // 核心邏輯:
        // 當我們列印一個物件的時候, 底層會呼叫物件的 toString 方法, 把物件變成字串
        // 然後再列印在控制檯上, 列印完畢換行處理

        // 思考: 預設情況下, 因為 Object 類中的 toString 方法返回的的是地址值
        // 所以, 預設情況下, 列印一個物件列印的就是地址值
        // 但是地址值對於我們是沒什麼意義的
        // 我想要看到物件內部的屬性值, 我們該怎麼辦?
        // 處理方案: 重寫父類 Object 類中的 toString 方法

        // toString 方法的結論:
        // 如果我們列印一個物件, 想要看到屬性值的話, 那麼就重寫 toString 方法就可以了
        // 在重寫的方法中, 把物件的屬性值進行拼接
    }
}

程式示例:

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

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

toString() 原始碼:

Object
圖3

getClass().getName() 用於獲取包名加類名, Integer.toHexString(hashCode()) 用於獲取物件的地址值, 然後進行了一些複雜的運算, 再轉換為 16 進位制, 再和前面的內容進行了拼接.

第二個方法: equals(), 程式示例:

public class Demo2 {
    public static void main(String[] args) {
        /*  public static boolean equals(Object obj)  比較兩個物件是否相等  */

        Student student1 = new Student();
        Student student2 = new Student();
        boolean res = student1.equals(student2);
        System.out.println(res);   // false
        // 這裡是用 Student 類的物件呼叫了 equals 方法,
        // 然而 Student 類顯然沒有 equals 方法, 因此是呼叫了 Object 裡面的 equals 方法.
        // Object 裡面的 equals 方法是用 == 號比較兩個物件的地址值是否相同.
        // 這兩個 Student 類的物件, 都是 new 出來的, 地址肯定是不一樣的. 因此用 equals 方法將返回 false.
        // 地址值的比較其實意義不大, 往往真正希望比較的是兩個物件內部的屬性值是否相同.
        // 現在父類 Object 的 equals 方法就已經不能滿足要求了, 於是就需要在子類 Student 中重寫 equals 方法.
        // 重寫這個 equals 方法, 並不需要我們自己手動書寫, 可以藉助 IDEA 的快捷鍵.
        // 過程很簡單, 一直 next 既可, 最終生成了 equals 方法和 hashCode 方法, 但是 hashCode 方法暫時用不上, 可以選擇先刪除.
    }
}

Object 類中的 equals() 方法:

Object
圖4

Object 裡面的 equals() 方法是用 == 號比較兩個物件的地址值是否相同.

重寫 Object 類裡面的 equals() 方法的過程:

Object
圖5

重寫之後的 Student 類:

import java.util.Objects;

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


    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    // 重寫之後的 equals 方法比較的就是物件內部的屬性值了.
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;  // 先比較是否為同一個物件, 如果是, 就沒必要比較內容了, 直接返回 true
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;  // 強制型別轉換
        return age == student.age && Objects.equals(name, student.name);
    }
}

此時比較兩個 Student 類的物件, 比較的就是屬性值了:

public class Demo2 {
    public static void main(String[] args) {
        /*  public static boolean equals(Object obj)  比較兩個物件是否相等  */

        Student student1 = new Student();
        Student student2 = new Student();
        Student student3 = new Student("zhangsan", 23);
        Student student4 = new Student("zhangsan", 23);

        boolean res1 = student1.equals(student2);
        boolean res2 = student3.equals(student4);
        System.out.println(res1);   // true
        System.out.println(res2);   // true
    }
}

程式示例:

public class Demo3 {
    public static void main(String[] args) {
        String s = "abc";
        StringBuilder sb = new StringBuilder("abc");

        // 這個 equals 方法是 s 呼叫的, 所以應該看 String 裡面的 equals 方法
        System.out.println(s.equals(sb));  // false
        // 字串中的 equals 方法, 先判斷引數是否為字串
        // 如果是字串, 再比較內部的屬性
        // 但是如果引數不是字串, 直接返回 alse
        // 這裡傳遞過來的顯然不是字串, 是一個 StringBuilder 物件


        // 這個 equals 方法是 sb 呼叫的, 所以應該看 StringBuilder 裡面的 equals 方法
        System.out.println(sb.equals(s));  // false
        // 在 StringBuilder 當中, 沒有重寫 equals 方法
        // 使用的是 Object 中的
        // 在 Object 當中預設是使用 == 號比較兩個物件的地址值
        // 而這裡的 s 和 sb 記錄的地址值是不一樣的, 所以結果返回 false
    }
}

String 類裡面的 equals() 方法:

Object
圖6

StringBuilder 類沒有重寫 equals() 方法:

Object
圖7

進入父類 AbstractStringBuilder 中檢視, 發現也沒有重寫 equals 方法, 則是使用了 Object 類的 equals() 方法:

Object
圖8

第 3 個方法: clone()

作用: 把 A 物件的屬性值完全複製給 B 物件, 也叫物件複製, 物件複製.

Object
圖9

可以看見, clone() 方法的修飾符為 protected, 所以 clone 方法只能被本包中的類和其他包中的子類使用, Object 類是寫在 java.lang 包的, 顯然我們不能把程式碼寫在 lang 包下, 所以如果想要使用 clone() 方法, 就只能自己重寫這個方法.

程式示例:

JavaBean 類:

// Cloneable
// 如果一個介面裡面沒有抽象方法
// 表示當前的介面是一個標記性介面
// 現在 Cloneable 表示一旦實現了, 那麼當前類的物件就可以被克降
// 如果沒有實現, 當前類的物件就不能克隆

import java.util.StringJoiner;

public class User implements Cloneable {
    private int id;
    private String username;
    private String password;
    private String path;
    private int[] data;

    public User() {
    }

    public User(int id, String username, String password, String path, int[] data) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.path = path;
        this.data = data;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public int[] getData() {
        return data;
    }

    public void setData(int[] data) {
        this.data = data;
    }

    public String toString() {
        return "角色編號為: " + id + ", 使用者名稱為: " + username + "密碼為: " + password + ", 遊戲圖片為:" + path + ", 進度:" + arrToString();
    }

    public String arrToString() {
        StringJoiner sj = new StringJoiner(", ", "[", "]");

        for (int i = 0; i < data.length; i++) {
            sj.add(data[i] + "");
        }
        return sj.toString();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 呼叫父類中的 clone 方法
        // 相當於讓 Java 幫我們克隆一個物件, 並把克隆之後的物件返回出去. 
        return super.clone();
    }
}

測試類:

public class Demo4 {
    public static void main(String[] args) throws CloneNotSupportedException {
        // protected object clone(int a) 物件克隆

        // 1.先建立一個物件
        int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0};
        User u1 = new User(1, "zhangsan", "1234qwer", "girl11", data);

        // 2.克隆物件
        // 細節:
        // 方法在底層會幫我們建立一個物件,並把原物件中的資料複製過去. 
        // 書寫細節:
        // 1.重寫 Object 中的 clone 方法
        // 2.讓 javabean 類實現 Cloneable 介面
        // 3.建立原物件並呼叫 clone 就可以了
        User u2 = (User) u1.clone();

        System.out.println(u1);
        System.out.println(u2);
    }
}

Java 中, 克隆有兩種方式.

第一種方式, 先建立一個新的物件, 再把原來的物件的屬性值全部複製到新物件中, 對於基本資料型別, 複製的是值, 對於引用型別的變數, 複製的是地址值.

如果有變數是記錄著陣列的地址的, 於是複製之後, 兩個變數指向了同一個陣列.

這種方式叫做淺克隆或者淺複製.

Object
圖10

第二種克隆方式, 也是會先建立一個物件, 對於各個屬性, 如果是基本資料型別, 還是會將值直接複製過來, 如果是引用資料型別, 就不會直接複製地址值了, 而是會重新將這些引用型變數的地址所指向的內容, 全部再建立一份出來, 比如將原有的陣列再建立出來一份一模一樣的, 新老陣列有不同的地址值, 新物件裡面的屬性記錄的就是新陣列的地址值. 對於字串, 只要不是 new 出來的, 都是統一放在串池中進行管理的, 是會被複用的.

這種方式叫做深克隆或者深複製.

Object
圖11

Object 中的 clone() 方法, 是淺克隆.

public class Demo4 {
    public static void main(String[] args) throws CloneNotSupportedException {
        // protected object clone(int a) 物件克隆

        // 1.先建立一個物件
        int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0};
        User u1 = new User(1, "zhangsan", "1234qwer", "girl11", data);

        User u2 = (User) u1.clone();

        // 驗證一件事情: Object 中的克隆是淺克隆
        // 想要進行深克隆, 就需要重寫 clone 方法並修改裡面的方法體
        int[] arr = u1.getData();
        arr[0] = 100;

        System.out.println(u1);
        System.out.println(u2);
    }
}

重寫改寫 Javabean 類:

// Cloneable
// 如果一個介面裡面沒有抽象方法
// 表示當前的介面是一個標記性介面
// 現在 Cloneable 表示一旦實現了, 那麼當前類的物件就可以被克降
// 如果沒有實現, 當前類的物件就不能克隆

import java.util.StringJoiner;

public class User implements Cloneable {
    private int id;
    private String username;
    private String password;
    private String path;
    private int[] data;

    public User() {
    }

    public User(int id, String username, String password, String path, int[] data) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.path = path;
        this.data = data;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public int[] getData() {
        return data;
    }

    public void setData(int[] data) {
        this.data = data;
    }

    public String toString() {
        return "角色編號為: " + id + ", 使用者名稱為: " + username + "密碼為: " + password + ", 遊戲圖片為:" + path + ", 進度:" + arrToString();
    }

    public String arrToString() {
        StringJoiner sj = new StringJoiner(", ", "[", "]");

        for (int i = 0; i < data.length; i++) {
            sj.add(data[i] + "");
        }
        return sj.toString();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 呼叫父類中的 clone 方法
        // 相當於讓 Java 幫我們克隆一個物件, 並把克隆之後的物件返回出去. 

        // 先把被克隆物件中的陣列獲取出來
        int[] data = this.data;
        // 建立新的陣列
        int[] newData = new int[data.length];
        // 複製陣列中的資料
        for (int i = 0; i < data.length; i++) {
            newData[i] = data[i];
        }
        // 呼叫父類中的方法克隆物件
        User u = (User) super.clone();
        // 因為父類中的克隆方法是淺克隆, 替換克隆出來物件中的陣列地址值
        u.data = newData;
        return u;
    }
}
Object
圖12

藉助第三方工具:

測試類:

public class Demo4 {
    public static void main(String[] args) throws CloneNotSupportedException {
        // protected object clone(int a) 物件克隆

        // 1.先建立一個物件
        int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0};
        User u1 = new User(1, "zhangsan", "1234qwer", "girl11", data);

        // 以後一般會用第三方工具進行克隆
        // 1. 第三方寫的程式碼匯入到專案中
        // 2. 編寫程式碼
        Gson gson = new Gson();
        // 把物件變成一個字串
        String s = gson.toJson(u1);
        // 再把字串變回物件就可以了
        User user = gson.fromJson(s, User.class);

        int[] arr = u1.getData();
        arr[0] = 100;

        // 列印物件
        System.out.println(user);
    }
}

相關文章