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