什麼是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 +
'}';
}