Object中方法的使用探究

Object類中自帶方法的使用探究

我們可以通過new一個Object對象查看其方法。
在這裏插入圖片描述
可以看見裏面有9個方法。

equals()

equals意爲比較。我們查看jdk1.8中自帶的src壓縮包中Object類並找到equals的源碼

   public boolean equals(Object obj) {
        return (this == obj);
    }

默認情況下它是將兩個對象進行地址的比較,如果地址相同,返回true否則返回false。
但是在很多情況下,我們自定義的類並不能直接調用Object 提供的equals方法來進行判斷兩個對象在實際意義上是否相同。

class Person{
	public int id;
	public String name;
	public int phoneNo;
	public Person() {
		// TODO Auto-generated constructor stub
	}
	Person(int id,String name,int phone){
		this.id=id;
		this.name=name;
		this.phoneNo=phone;
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "姓名:"+this.name+" 身份證號碼"+this.id+" 手機號碼"+this.phoneNo;
	}
	@Override
	public int hashCode() {
		// TODO Auto-generated method stub
		return this.name.hashCode()+this.id*31+this.phoneNo*31;
	}
	@Override
	public boolean equals(Object arg0) {
		// TODO Auto-generated method stub
		if (arg0==this) {
			return true;
		}
		if (arg0 instanceof Person) {
			Person person=(Person)arg0;
			if (person.hashCode()==this.hashCode()) {
				return true;
			}
			if (person.getId()==this.getId()&&person.getName().equals(this.getName())&&person.getPhoneNo()==this.getPhoneNo()) {
				return true;
			}
		}
		return false;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getPhoneNo() {
		return phoneNo;
	}
	public void setPhoneNo(int phoneNo) {
		this.phoneNo = phoneNo;
	}
}
public class DEmo {
	public static void main(String[] args) {
		Person p1=new Person(123456,"王邦彥",13311111);
		Person p2=p1;
		Person p3=new Person(123456,"王邦彥",13311111);
		Person p4=new Person(123456,"王邦彥",13311112);
		if (p1.equals(p2)) {
			System.out.println(1);
		}
		if (p1.equals(p3)) {
			System.out.println(3);
		}
		if (p1.equals(p4)) {
			System.out.println(4);
		}
	}
}

此時我們有4個引用對象,其中p1指向(123456,“王邦彥”,13311111)這個對象,p2只是將p1的引用地址拿了過來所以一樣指向p1的對象,p3指向一個new的(123456,“王邦彥”,13311111)對象,p4指向(123456,“王邦彥”,13311112)對象。如果按照原來Object提供的默認equals方法,那麼p3和p1是返回false的,這不滿足我們實際生產需要。所以我們需要將equals方法進行重寫(覆寫)。

public boolean equals(Object arg0) {
		// TODO Auto-generated method stub
		if (arg0==this) {
			return true;
		}
		if (arg0 instanceof Person) {
			Person person=(Person)arg0;
			if (person.hashCode()==this.hashCode()) {
				return true;
			}
			if (person.getId()==this.getId()&&person.getName().equals(this.getName())&&person.getPhoneNo()==this.getPhoneNo()) {
				return true;
			}
		}
		return false;
	}

重寫時套路都是一個模板,先進行地址判斷,如果和自己相同那肯定是true,然後判斷對象是不是自己所在類的實例,如果是則將其轉型爲自己類對象,判斷hash值是否一致,最後再使用“殺手鐗”判斷類中各個屬性是否相同。注意代碼中person.getName().equals調用的是String類中重寫的equals方法。
我們再來看下String類是如何重寫equals方法的:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
    ```

是不是同樣的套路?,不過String類的“殺手鐗”是通過將字符數組中的字符進行單獨比較。

getClass()

從字面上看,應該是獲取該對象的類。
查看源碼

public final native Class<?> getClass();

可以看到,它是有native關鍵字進行修飾的,什麼是native關鍵字呢?凡是一種語言,都希望是純。比如解決某一個方案都喜歡就單單這個語言來寫即可。Java平臺有個用戶和本地C代碼進行互操作的API,稱爲Java Native Interface (Java本地接口)。native的意思就是通知操作系統,這個函數你必須給我實現,因爲我要使用。所以native關鍵字的函數都是操作系統實現的,java只能調用。[1]
既然從源碼方面我們看不到什麼,那麼只好實際調用一番來觀察getClass到底是什麼東西。以equals的代碼爲例

public class DeepCopy {
	public static void main(String[] args) throws Exception  {
		School_D school_D=new School_D("武漢大學");
		Person_D p1=new Person_D("小明", 11, "張家界",school_D);
		System.out.println(p1.getClass());
		System.out.println(school_D.getClass());
	}
}

結果是 在這裏插入圖片描述這和我們從字面上看是一致的。
以下是我對其實現的猜測,如有錯誤還望大家能夠指出。
jvm在加載一個類的時候,會先在方法區中尋找是否有這個類信息,如果沒有則到/path路徑下尋找.class文件,找不到則產生異常ClassNotFonudException,找到了則將其通過類加載器加載至方法區。而實例一個類對象的時候,會將此類的類加載器的引用一併存在方法區中。getClass方法很可能是通過這種方式找到類信息。

hashCode()

從字面上看是獲取此對象的哈希值
源碼

 public native int hashCode();

和getClass一樣都是本地方法。

public class DeepCopy {
	public static void main(String[] args) throws Exception  {
		School_D school_D=new School_D("武漢大學");
		Person_D p1=new Person_D("小明", 11, "張家界",school_D);
		System.out.println(p1.hashCode());
		System.out.println(school_D.hashCode());
	}
}

在這裏插入圖片描述
可以看出,hashCode方法是將對象進行hash運算,產生一串int類型的值。

HashMap中內部類Node裏面有個hash屬性,指的就是對象的hash值,那它又是怎麼利用hashCode方法呢,觀察源碼

 public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }
        
    /**
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

它將key的hash值和value的hash值進行了異或操作,進一步避免了哈希衝突的發生。充分利用了HashMap中桶的數量。

Notify(),NotifyAll(),Wait()

用於多線程編程中,對線程進行休眠及喚醒操作。
一個線程中的某個同步共享對象執行wait方法,該線程就釋放了該對象的對象鎖,進入對象等待池,等待被喚醒;在另一個線程中,這個同步共享變量執行notify方法,喚醒因wait而正在等待使用該對象的線程,使其進入對象鎖等待池,有機會獲得對象鎖,等到獲取對象鎖,該線程獲得CPU調度,繼續運行。需要注意wait、notify以及notifyall方法必須在synchronized代碼塊中,切記![2]

public class WaitAndNotify{
	ReentrantLock rLock=new ReentrantLock();
	private List<Integer> MyList=new ArrayList<Integer>();

	public static void main(String[] args) {
		WaitAndNotify waitAndNotify=new WaitAndNotify();
		threadA t1=new threadA(waitAndNotify.rLock,waitAndNotify.MyList);
		threadB t2=new threadB(waitAndNotify.rLock,waitAndNotify.MyList);
		Thread thread1=new Thread(t1);
		Thread thread2=new Thread(t2);
		thread1.start();
		thread2.start();
		
	}
}
class threadA implements Runnable{
	ReentrantLock rLock=null;
	List<Integer> MyList=new ArrayList<Integer>();

	public threadA(ReentrantLock lock,List<Integer> list) {
		// TODO Auto-generated constructor stub
		this.rLock=lock;
	}
	public void run() {
        try {
            synchronized (rLock) {
            	
                if (MyList.size() != 5) {
                    System.out.println("wait begin "
                            + System.currentTimeMillis());
                    Thread.sleep(2000);
                    rLock.wait();				//wait()方法執行後,將鎖釋放,threadB獲得鎖開始循環
                    System.out.println("wait end  "
                            + System.currentTimeMillis());
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
class threadB implements Runnable{
	ReentrantLock rLock=null;
	List<Integer> MyList=new ArrayList<Integer>();
	public threadB(ReentrantLock lock,List<Integer> list) {
		// TODO Auto-generated constructor stub
		this.rLock=lock;
	}
	public void run() {
        try {
            synchronized (rLock) {
                for (int i = 0; i < 10; i++) {
                    MyList.add(i);
                    if (MyList.size() == 5) {
                        rLock.notify();                     //notify方法調用之後,不會馬上釋放鎖,而是運行完該同步方法或者是運行完該同步代碼塊的代碼
                        System.out.println("已發出通知!");
                    }
                    System.out.println("添加了" + (i + 1) + "個元素!");
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

在這裏插入圖片描述

notify之後並不像wait一樣立即釋放資源,而是等線程剩餘任務完成後才交出鎖對象和CPU。
wait()方法可以傳入參數也可以不傳入參數,傳入參數就是在參數結束的時間後開始等待,不傳入參數就是直接等待。

Sleep()與Wait()區別

sleep方法是Thread中的靜態方法。public static native void sleep(long millis) throws InterruptedException;導致此線程暫停執行指定時間,給其他線程執行機會,但是依然保持着監控狀態,過了指定時間會自動恢復,調用sleep方法不會釋放鎖對象。
當調用sleep方法後,當前線程進入阻塞狀態。目的是讓出CPU給其他線程運行的機會。但是由於sleep方法不會釋放鎖對象,所以在一個同步代碼塊中調用這個方法後,線程雖然休眠了,但其他線程無法訪問它的鎖對象。這是因爲sleep方法擁有CPU的執行權,它可以自動醒來無需喚醒。而當sleep()結束指定休眠時間後,這個線程不一定立即執行,因爲此時其他線程可能正在運行。
wait方法是Object類裏的方法,當一個線程執行到wait()方法時,它就進入到一個和該對象相關的等待池中,同時釋放了鎖對象,等待期間可以調用裏面的同步方法,其他線程可以訪問,等待時不擁有CPU的執行權,否則其他線程無法獲取執行權。當一個線程執行了wait方法後,必須調用notify或者notifyAll方法才能喚醒,而且是隨機喚醒,若是被其他線程搶到了CPU執行權,該線程會繼續進入等待狀態。由於鎖對象可以時任意對象,所以wait方法必須定義在Object類中,因爲Obeject類是所有類的基類。
[3]

toString()

返回對象的信息,默認情況下是返回對象的類信息+hash值(16進制)

public class DeepCopy {
	public static void main(String[] args) throws Exception  {
		School_D school_D=new School_D("武漢大學");
		Person_D p1=new Person_D("小明", 11, "張家界",school_D);
		System.out.println(p1.hashCode());
		System.out.println(p1.toString());
		int a=p1.hashCode();
		System.out.println(a);
	}
}

在這裏插入圖片描述
當有特殊需要時,可以對其進行重寫。

public class DeepCopy {
	public static void main(String[] args) throws Exception  {
		School_D school_D=new School_D("武漢大學");
		Person_D p1=new Person_D("小明", 11, "張家界",school_D);
		System.out.println(p1.toString());
	}
}
@Override
	public String toString() {
		return "姓名是:"+this.name+"年齡:"+this.age+"住址"+this.address+"  學校是"+this.school;	
	}

在這裏插入圖片描述

參考資料

[1]https://www.cnblogs.com/KingIceMou/p/7239668.html
[2]https://www.cnblogs.com/hzhtracy/p/4636185.html
[3]https://www.cnblogs.com/lyx210019/p/9427146.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章