對象老祖宗Object:我有話說

什麼是Object類

Object是所有Java類的祖先,位於 java.lang 包下,(這個包中所有類在使用時無需手動導入,系統會在程序編譯期間自動導入)。當一個類沒有直接繼承某個類時,默認繼承Object類,也就是說任何類都直接或間接繼承Object,Object 類中能訪問的方法在所有類中都可以調用。

也就是下面是相等的。

public class User {
}

public class User  extends Object{
}

Object類下所有方法。
在這裏插入圖片描述

clone()

用於對象克隆,是指創建此對象的完整副本,並使用當前對象的相應字段的內容初始化新實例的所有字段,也就是克隆出來的對象中所有字段是等於原對象中所有字段的。

public class User extends Object implements Cloneable {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected User clone()  {
        User user =null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }
    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}


public class Main {
    public static void main(String[] args) {
        User user =new User();
        user.setAge(11);
        user.setName("name");
        User clone =user.clone();
        System.out.println(user ==clone);
        System.out.println(user.toString());
        System.out.println(clone.toString());
    }
}

運行結果:

false
User{age=11, name='name'}
User{age=11, name='name'}

首先被克隆的對象一定要實現Cloneable接口,否則會報CloneNotSupportedException錯誤。

從第一個輸出可以發現這兩個對象不是同一個對象,而內容都是一樣的。但上面的克隆被稱爲淺克隆,還有一種是深克隆。淺克隆是指創建一個新對象,新對象的屬性和原來對象完全相同,對於非基本類型屬性,仍指向原有屬性所指向的對象的內存地址,而深克隆創建的新對象屬性中引用的其他對象也會被克隆,不再指向原有對象地址。

如下代碼,增加一個Dog類。

package com.hxl;

public class User extends Object implements Cloneable {
    private int age;
    private String name;
    private Dog dog;

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    protected User clone()  {
        User user =null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
public class Dog {
    private String dogName;

    public String getDogName() {
        return dogName;
    }

    public void setDogName(String dogName) {
        this.dogName = dogName;
    }
}

public class Main {
    public static void main(String[] args) {
        User user =new User();
        user.setAge(11);
        user.setName("name");
        user.setDog(new Dog());
        User clone =user.clone();
        System.out.println(user.getDog() ==clone.getDog());
        System.out.println(user.toString());
        System.out.println(clone.toString());
    }
}

運行結果:

true
User{age=11, name='name'}
User{age=11, name='name'}

從第一個輸出可以看到,Dog對象是相等的,如果克隆出來的對象修改其中的Dog屬性,那麼原來對象中的Dog屬性也會被改變,如果要完成深度克隆,可以這樣做,

@Override
protected User clone()  {
    User user =null;
    try {
        user = (User) super.clone();
        Dog cloneDog = (Dog) user.getDog().clone();
        user.setDog(cloneDog);
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return user;
}

同樣Dog也要實現Cloneable接口。這樣克隆出來的對象修改其中Dog實例,原來對象是不會發生變化的。

equals()

用來判斷對象是否相等,但在 Object 類中,== 運算符和 equals 方法是等價的,都是比較兩個對象的引用是否相等。 也就是如果不重寫 equals 方法,那麼在比較對象的時候就是調用 == 運算符比較兩個對象。所以,是不是有點衝動想看String.equals方法是怎麼比較字符串的?
在這裏插入圖片描述
在做一個小測試,如果我們不重寫它的話,下面判斷只會輸出false。

public class Main {
    public static void main(String[] args) {
        User user1 =new User(11,"張三");
        User user2 =new User(11,"張三");
        System.out.println(user1.equals(user2));
    }
}

重寫後就不一樣了。

public class User extends Object implements Cloneable {
	........
	@Override
	public boolean equals(Object o) {
	    if (this == o) return true;
	    if (!(o instanceof User)) return false;
	    User user = (User) o;
	    return getAge() == user.getAge() &&
	            Objects.equals(getName(), user.getName()) &&
	            Objects.equals(getDog(), user.getDog());
	}
}

但在 在Java規範中,對 equals 方法的使用必須遵循以下幾個原則:
1、自反性:對於任何非空引用值 x,x.equals(x) 都應返回 true。
2、對稱性:對於任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true。
3、傳遞性:對於任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,並且 y.equals(z) 返回 true,那麼 x.equals(z) 應返回 true。
4、一致性:對於任何非空引用值 x 和 y,多次調用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改
5、對於任何非空引用值 x,x.equals(null) 都應返回 false。

finalize()

一般由GC在回收對象之前自動調用,子類可以覆蓋該方法以實現資源清理工作,類似於Android中Activity的onDestroy方法。但是Java語言規範並不保證finalize方法會被及時地執行、而且根本不會保證它們會被執行,雖然System.gc()與System.runFinalization()方法增加了finalize方法執行的機會,但不可盲目依賴它們。

public class User extends Object implements Cloneable {
	@Override
	protected void finalize() throws Throwable {
	    System.out.println("finalize");
	    super.finalize();
	}
}
public class Main {
    public static void main(String[] args)  {
        User user1 =new User(11,"張三");
        User user2 =new User(11,"張三");
        user1=null;
        System.gc();
    }
}

運行結果:

finalize

或者是

public class Main {
    public static void main(String[] args) {
        User user1 = new User(11, "張三");
        User user2 = new User(11, "張三");
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.gc();
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

getClass()

這是一個用 native 關鍵字修飾的方法,首先說什麼是Class對象,Java每個class都有一個相應的Class對象。也就是說,當我們編寫一個類,編譯完成後,在生成的.class文件中,就會產生一個Class對象,用於表示這個類的類型信息。

getClass()就是獲得此實例對象的Class,可以通過返回的Class對象獲取相關信息,也可以通過Class.forName獲取。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        User user = new User(11, "name");
        Class<? extends User> userClass1 = user.getClass();
        Class<?> userClass2 = Class.forName("com.hxl.User");
        System.out.println(userClass1.equals(userClass2));
    }
}

可以獲取此類的字段、方法等信息,進而更改,詳細可以學習Java反射知識。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        User user = new User(11, "name");
        Class<? extends User> userClass1 = user.getClass();
        Field[] declaredFields = userClass1.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField.getName());
        }
        System.out.println("----------------");
        Field name = userClass1.getDeclaredField("name");
        name.setAccessible(true);
        name.set(user,"張三");
        System.out.println(user.getName());
    }
}

運行結果

age
name
dog
----------------
張三

hashCode()

返回此對象的哈希碼,在HashSet、HashMap以及HashTable中,內部會經常用到。

下面這段話摘自Effective Java一書:

在程序執行期間,只要equals方法的比較操作用到的信息沒有被修改,那麼對這同一個對象調用多次,hashCode方法必須始終如一地返回同一個整數。

如果兩個對象根據equals方法比較是相等的,那麼調用兩個對象的hashCode方法必須返回相同的整數結果。

如果兩個對象根據equals方法比較是不等的,則hashCode方法不一定得返回不同的整數。

wait()、notify()、notifyAll()

爲了支持多線程之間的協作,JDK提供了兩個非常重要的方法:等待wait()方法和通知notify()方法。

當在一個對象實例上調用wait()方法後,當前線程就會在這個對象上等待。比如,在線程A中,調用了obj.wait()方法,那麼線程A就會停止繼續執行,轉爲等待狀態。直到其他線程調用了obj.notify()方法爲止。

他是這樣工作的,如果一個線程調用了object. wait()方法,那麼它就會進入object對象的等待隊列,這個等持隊列中,可能會有多個線程,因爲系統運行多個線程同時等待某一個對象。 當object.notify()方法被調用時,他就會從這個等待隊列中隨機選擇一個線程,並將其喚醒,這個選擇是不公平的,並不是先等待的線程就會被優先選擇。

除了notify()方法外,還有notifyAll()方法,他和notify()功能基本一致,區別是,他會喚醒在這個等待隊列中的所有等待線程。

另外,在調用這三個方法時,並不能隨意調用,必須包含在synchronized語句中,都先要獲取目標對象的監視器。

public class Main {
    public static void main(String[] args) {
        User user = new User(11, "name");
        new Thread(() -> {
            synchronized (user) {
                try {
                    System.out.println("wait");
                    user.wait();
                    System.out.println("wait end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            synchronized (user) {
                System.out.println("notify");
                user.notify();
                System.out.println("notify end");
            }
        }).start();
    }
}

運行結果如下(無論怎麼運行,都會是這個結果)

wait
notify
notify end
wait end

首先要等待的線程獲取到user的對象鎖,然後wait()進行等待,並釋放user的對象鎖!!!,不然要通知的線程是無法進入代碼塊的,通知的線程拿到user的對象鎖後,執行notify(),當等待線的程得到notify(),還是會嘗試重新獲取user的對象鎖,也就是如果通知線程沒釋放的話,等待線程也沒辦法繼續下去。

notifyAll()的用法,運行之後兩個等待線程也都會接到通知,進而結束。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        User user = new User(11, "name");
        new Thread(() -> {
            synchronized (user) {
                try {
                    System.out.println("wait");
                    user.wait();
                    System.out.println("wait end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(() -> {
            synchronized (user) {
                try {
                    System.out.println("wait2");
                    user.wait();
                    System.out.println("wait2 end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        Thread.sleep(1000);
        synchronized (user){
            user.notifyAll();
        }
    }
}

toString()

用的最多的可能就是它了,返回一個"以文本方式表示"此對象的字符串,在打印對象的時候,其實就是調用object.toString()。

public class Main {
    public static void main(String[] args)  {
        User user = new User(11, "name");
        System.out.println(user);
    }
}

默認的輸出是類名@十六進制的hashCode

 public String toString() {
     return getClass().getName() + "@" + Integer.toHexString(hashCode());
 }

重寫他也很容易,也可以藉助開發工具快速生成(Alt+Insert)。
在這裏插入圖片描述
想返回什麼返回什麼。

 @Override
 public String toString() {
     return "User{" +
             "age=" + age +
             ", name='" + name + '\'' +
             ", dog=" + dog +
             '}';
 }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章