java中object方法有:1、【getClass()】是一個public的方法;2、【hashCode()】是一個public的方法,可以直接通過對象調用;3、【equals()】用于比較當前對象與目標對象是否相等。
10余年的深圳網站建設經驗,針對設計、前端、開發、售后、文案、推廣等六對一服務,響應快,48小時及時工作處理。全網營銷推廣的優勢是能夠根據用戶設備顯示端的尺寸不同,自動調整深圳建站的顯示方式,使網站能夠適用不同顯示終端,在瀏覽器中調整網站的寬度,無論在任何一種瀏覽器上瀏覽網站,都能展現優雅布局與設計,從而大程度地提升瀏覽體驗。創新互聯公司從事“深圳網站設計”,“深圳網站推廣”以來,每個客戶項目都認真落實執行。
相關免費學習推薦:java基礎教程
java中object方法有:
一、引言
Object是java所有類的基類,是整個類繼承結構的頂端,也是最抽象的一個類。大家天天都在使用toString()、equals()、hashCode()、waite()、notify()、getClass()等方法,或許都沒有意識到是Object的方法,也沒有去看Object還有哪些方法以及思考為什么這些方法要放到Object中。本篇就每個方法具體功能、重寫規則以及自己的一些理解。
二、Object方法詳解
Object中含有:registerNatives()、getClass()、hashCode()、equals()、clone()、toString()、notify()、notifyAll()、wait(long)、wait(long,int)、wait()、finalize()共十二個方法。這個順序是按照Object類中定義方法的順序列舉的,下面我也會按照這個順序依次進行講解。
1.1、registerNatives()
public class Object { private static native void registerNatives(); static { registerNatives(); } }
什么鬼?哈哈哈,我剛看到這方法,一臉懵逼。從名字上理解,這個方法是注冊native方法(本地方法,由JVM實現,底層是C/C++實現的)向誰注冊呢?當然是向JVM,當有程序調用到native方法時,JVM才好去找到這些底層的方法進行調用。
Object中的native方法,并使用registerNatives()向JVM進行注冊。(這屬于JNI的范疇,9龍暫不了解,有興趣的可自行查閱。)
static JNINativeMethod methods[] = { {"hashCode", "()I", (void *)&JVM_IHashCode}, {"wait", "(J)V", (void *)&JVM_MonitorWait}, {"notify", "()V", (void *)&JVM_MonitorNotify}, {"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll}, {"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone}, };
為什么要使用靜態方法,還要放到靜態塊中呢?
我們知道在類初始化的時候,會依次從父類到本類的類變量及類初始化塊中的類變量及方法按照定義順序放到< clinit>方法中,這樣可以保證父類的類變量及方法的初始化一定先于子類。所以當子類調用相應native方法,比如計算hashCode時,一定可以保證能夠調用到JVM的native方法。
1.2、getClass()
public final native Class getClass():這是一個public的方法,我們可以直接通過對象調用。
類加載的第一階段類的加載就是將.class文件加載到內存,并生成一個java.lang.Class對象的過程。getClass()方法就是獲取這個對象,這是當前類的對象在運行時類的所有信息的集合。這個方法是反射三種方式之一。
1.2.1、反射三種方式:
對象的getClass();
類名.class;
Class.forName(); class extends ObjectTest { private void privateTest(String str) { System.out.println(str); } public void say(String str) { System.out.println(str); } } public class ObjectTest { public static void main(String[] args) throws Exception { ObjectTest = new (); //獲取對象運行的Class對象 Class extends ObjectTest> aClass = .getClass(); System.out.println(aClass); //getDeclaredMethod這個方法可以獲取所有的方法,包括私有方法 Method privateTest = aClass.getDeclaredMethod("privateTest", String.class); //取消java訪問修飾符限制。 privateTest.setAccessible(true); privateTest.invoke(aClass.newInstance(), "private method test"); //getMethod只能獲取public方法 Method say = aClass.getMethod("say", String.class); say.invoke(aClass.newInstance(), "Hello World"); } } //輸出結果: //class test. //private method test //Hello World
反射主要用來獲取運行時的信息,可以將java這種靜態語言動態化,可以在編寫代碼時將一個子對象賦值給父類的一個引用,在運行時通過反射可以或許運行時對象的所有信息,即多態的體現。對于反射知識還是很多的,這里就不展開講了。
1.3、hashCode()
public native int hashCode(); 這是一個public的方法,所以子類可以重寫它。這個方法返回當前對象的hashCode值,這個值是一個整數范圍內的(-2^31 ~ 2^31 - 1)數字。
對于hashCode有以下幾點約束
在 Java 應用程序執行期間,在對同一對象多次調用 hashCode 方法時,必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的信息沒有被修改;
如果兩個對象 x.equals(y) 方法返回true,則x、y這兩個對象的hashCode必須相等。
如果兩個對象x.equals(y) 方法返回false,則x、y這兩個對象的hashCode可以相等也可以不等。但是,為不相等的對象生成不同整數結果可以提高哈希表的性能。
默認的hashCode是將內存地址轉換為的hash值,重寫過后就是自定義的計算方式;也可以通過System.identityHashCode(Object)來返回原本的hashCode。
public class HashCodeTest { private int age; private String name; @Override public int hashCode() { Object[] a = Stream.of(age, name).toArray(); int result = 1; for (Object element : a) { result = 31 * result + (element == null ? 0 : element.hashCode()); } return result; } }
推薦使用Objects.hash(Object… values)方法。相信看源碼的時候,都看到計算hashCode都使用了31作為基礎乘數,為什么使用31呢?我比較贊同與理解result * 31 = (result<<5) - result。JVM底層可以自動做優化為位運算,效率很高;還有因為31計算的hashCode沖突較少,利于hash桶位的分布。
1.4、equals()
public boolean equals(Object obj);用于比較當前對象與目標對象是否相等,默認是比較引用是否指向同一對象。為public方法,子類可重寫。
public class Object{ public boolean equals(Object obj) { return (this == obj); } }
為什么需要重寫equals方法?
因為如果不重寫equals方法,當將自定義對象放到map或者set中時;如果這時兩個對象的hashCode相同,就會調用equals方法進行比較,這個時候會調用Object中默認的equals方法,而默認的equals方法只是比較了兩個對象的引用是否指向了同一個對象,顯然大多數時候都不會指向,這樣就會將重復對象存入map或者set中。這就破壞了map與set不能存儲重復對象的特性,會造成內存溢出。
重寫equals方法的幾條約定:
自反性:即x.equals(x)返回true,x不為null;
對稱性:即x.equals(y)與y.equals(x)的結果相同,x與y不為null;
傳遞性:即x.equals(y)結果為true, y.equals(z)結果為true,則x.equals(z)結果也必須為true;
一致性:即x.equals(y)返回true或false,在未更改equals方法使用的參數條件下,多次調用返回的結果也必須一致。x與y不為null。
如果x不為null, x.equals(null)返回false。
我們根據上述規則來重寫equals方法。
public class EqualsTest{ private int age; private String name; //省略get、set、構造函數等 @Override public boolean equals(Object o) { //先判斷是否為同一對象 if (this == o) { return true; } //再判斷目標對象是否是當前類及子類的實例對象 //注意:instanceof包括了判斷為null的情況,如果o為null,則返回false if (!(o instanceof )) { return false; } that = () o; return age == that.age && Objects.equals(name, that.name); } public static void main(String[] args) throws Exception { EqualsTest1 equalsTest1 = new EqualsTest1(23, "9龍"); EqualsTest1 equalsTest12 = new EqualsTest1(23, "9龍"); EqualsTest1 equalsTest13 = new EqualsTest1(23, "9龍"); System.out.println("-----------自反性----------"); System.out.println(equalsTest1.equals(equalsTest1)); System.out.println("-----------對稱性----------"); System.out.println(equalsTest12.equals(equalsTest1)); System.out.println(equalsTest1.equals(equalsTest12)); System.out.println("-----------傳遞性----------"); System.out.println(equalsTest1.equals(equalsTest12)); System.out.println(equalsTest12.equals(equalsTest13)); System.out.println(equalsTest1.equals(equalsTest13)); System.out.println("-----------一致性----------"); System.out.println(equalsTest1.equals(equalsTest12)); System.out.println(equalsTest1.equals(equalsTest12)); System.out.println("-----目標對象為null情況----"); System.out.println(equalsTest1.equals(null)); } } //輸出結果 //-----------自反性---------- //true //-----------對稱性---------- //true //true //-----------傳遞性---------- //true //true //true //-----------一致性---------- //true //true //-----目標對象為null情況---- //false
從以上輸出結果驗證了我們的重寫規定是正確的。
注意:instanceof 關鍵字已經幫我們做了目標對象為null返回false,我們就不用再去顯示判斷了。
建議equals及hashCode兩個方法,需要重寫時,兩個都要重寫,一般都是將自定義對象放至Set中,或者Map中的key時,需要重寫這兩個方法。
1.4、clone()
protected native Object clone() throws CloneNotSupportedException;
此方法返回當前對象的一個副本。
這是一個protected方法,提供給子類重寫。但需要實現Cloneable接口,這是一個標記接口,如果沒有實現,當調用object.clone()方法,會拋出CloneNotSupportedException。
public class CloneTest implements Cloneable { private int age; private String name; //省略get、set、構造函數等 @Override protected CloneTest clone() throws CloneNotSupportedException { return (CloneTest) super.clone(); } public static void main(String[] args) throws CloneNotSupportedException { CloneTest cloneTest = new CloneTest(23, "9龍"); CloneTest clone = cloneTest.clone(); System.out.println(clone == cloneTest); System.out.println(cloneTest.getAge()==clone.getAge()); System.out.println(cloneTest.getName()==clone.getName()); } } //輸出結果 //false //true //true
從輸出我們看見,clone的對象是一個新的對象;但原對象與clone對象的String類型的name卻是同一個引用,這表明,super.clone方法對成員變量如果是引用類型,進行是淺拷貝。
那什么是淺拷貝?對應的深拷貝?
淺拷貝:拷貝的是引用。
深拷貝:新開辟內存空間,進行值拷貝。
那如果我們要進行深拷貝怎么辦呢?看下面的例子。
class Person implements Cloneable{ private int age; private String name; //省略get、set、構造函數等 @Override protected Person clone() throws CloneNotSupportedException { Person person = (Person) super.clone(); //name通過new開辟內存空間 person.name = new String(name); return person; } } public class CloneTest implements Cloneable { private int age; private String name; //增加了person成員變量 private Person person; //省略get、set、構造函數等 @Override protected CloneTest clone() throws CloneNotSupportedException { CloneTest clone = (CloneTest) super.clone(); clone.person = person.clone(); return clone; } public static void main(String[] args) throws CloneNotSupportedException { CloneTest cloneTest = new CloneTest(23, "9龍"); Person person = new Person(22, "路飛"); cloneTest.setPerson(person); CloneTest clone = cloneTest.clone(); System.out.println(clone == cloneTest); System.out.println(cloneTest.getAge() == clone.getAge()); System.out.println(cloneTest.getName() == clone.getName()); Person clonePerson = clone.getPerson(); System.out.println(person == clonePerson); System.out.println(person.getName() == clonePerson.getName()); } } //輸出結果 //false //true //true //false //false
可以看到,即使成員變量是引用類型,我們也實現了深拷貝。如果成員變量是引用類型,想實現深拷貝,則成員變量也要實現Cloneable接口,重寫clone方法。
1.5、toString()
public String toString();這是一個public方法,子類可重寫,建議所有子類都重寫toString方法,默認的toString方法,只是將當前類的全限定性類名+@+十六進制的hashCode值。
public class Object{ public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } }
我們思考一下為什么需要toString方法?
我這么理解的,返回當前對象的字符串表示,可以將其打印方便查看對象的信息,方便記錄日志信息提供調試。
我們可以選擇需要表示的重要信息重寫到toString方法中。為什么Object的toString方法只記錄類名跟內存地址呢?因為Object沒有其他信息了,哈哈哈。
1.6、wait()/ wait(long)/ waite(long,int)
這三個方法是用來線程間通信用的,作用是阻塞當前線程,等待其他線程調用notify()/notifyAll()方法將其喚醒。這些方法都是public final的,不可被重寫。
注意:
此方法只能在當前線程獲取到對象的鎖監視器之后才能調用,否則會拋出IllegalMonitorStateException異常。
調用wait方法,線程會將鎖監視器進行釋放;而Thread.sleep,Thread.yield()并不會釋放鎖。
wait方法會一直阻塞,直到其他線程調用當前對象的notify()/notifyAll()方法將其喚醒;而wait(long)是等待給定超時時間內(單位毫秒),如果還沒有調用notify()/nofiyAll()會自動喚醒;waite(long,int)如果第二個參數大于0并且小于999999,則第一個參數+1作為超時時間;
public final void wait() throws InterruptedException { wait(0); } public final native void wait(long timeout) throws InterruptedException; public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); }
1.7、notify()/notifyAll()
前面說了,如果當前線程獲得了當前對象鎖,調用wait方法,將鎖釋放并阻塞;這時另一個線程獲取到了此對象鎖,并調用此對象的notify()/notifyAll()方法將之前的線程喚醒。這些方法都是public final的,不可被重寫。
public final native void notify(); 隨機喚醒之前在當前對象上調用wait方法的一個線程
public final native void notifyAll(); 喚醒所有之前在當前對象上調用wait方法的線程
下面我們使用wait()、notify()展示線程間通信。假設9龍有一個賬戶,只要9龍一發工資,就被女朋友給取走了。
//賬戶 public class Account { private String accountNo; private double balance; private boolean flag = false; public Account() { } public Account(String accountNo, double balance) { this.accountNo = accountNo; this.balance = balance; } /** * 取錢方法 * * @param drawAmount 取款金額 */ public synchronized void draw(double drawAmount) { try { if (!flag) { //如果flag為false,表明賬戶還沒有存入錢,取錢方法阻塞 wait(); } else { //執行取錢操作 System.out.println(Thread.currentThread().getName() + " 取錢" + drawAmount); balance -= drawAmount; //標識賬戶已沒錢 flag = false; //喚醒其他線程 notify(); } } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void deposit(double depositAmount) { try { if (flag) { //如果flag為true,表明賬戶已經存入錢,取錢方法阻塞 wait(); } else { //存錢操作 System.out.println(Thread.currentThread().getName() + " 存錢" + depositAmount); balance += depositAmount; //標識賬戶已存入錢 flag = true; //喚醒其他線程 notify(); } } catch (InterruptedException e) { e.printStackTrace(); } } } //取錢者 public class DrawThread extends Thread { private Account account; private double drawAmount; public DrawThread(String name, Account account, double drawAmount) { super(name); this.account = account; this.drawAmount = drawAmount; } @Override public void run() { //循環6次取錢 for (int i = 0; i < 6; i++) { account.draw(drawAmount); } } } //存錢者 public class DepositThread extends Thread { private Account account; private double depositAmount; public DepositThread(String name, Account account, double depositAmount) { super(name); this.account = account; this.depositAmount = depositAmount; } @Override public void run() { //循環6次存錢操作 for (int i = 0; i < 6; i++) { account.deposit(depositAmount); } } } //測試 public class DrawTest { public static void main(String[] args) { Account brady = new Account("9龍", 0); new DrawThread("女票", brady, 10).start(); new DepositThread("公司", brady, 10).start(); } } //輸出結果 //公司 存錢10.0 //女票 取錢10.0 //公司 存錢10.0 //女票 取錢10.0 //公司 存錢10.0 //女票 取錢10.0
例子中我們通過一個boolean變量來判斷賬戶是否有錢,當取錢線程來判斷如果賬戶沒錢,就會調用wait方法將此線程進行阻塞;這時候存錢線程判斷到賬戶沒錢, 就會將錢存入賬戶,并且調用notify()方法通知被阻塞的線程,并更改標志;取錢線程收到通知后,再次獲取到cpu的調度就可以進行取錢。反復更改標志,通過調用wait與notify()進行線程間通信。實際中我們會時候生產者消費者隊列會更簡單。
注意:調用notify()后,阻塞線程被喚醒,可以參與鎖的競爭,但可能調用notify()方法的線程還要繼續做其他事,鎖并未釋放,所以我們看到的結果是,無論notify()是在方法一開始調用,還是最后調用,阻塞線程都要等待當前線程結束才能開始。
為什么wait()/notify()方法要放到Object中呢?
因為每個對象都可以成為鎖監視器對象,所以放到Object中,可以直接使用。
1.8、finalize()
protected void finalize() throws Throwable ;
此方法是在垃圾回收之前,JVM會調用此方法來清理資源。此方法可能會將對象重新置為可達狀態,導致JVM無法進行垃圾回收。
我們知道java相對于C++很大的優勢是程序員不用手動管理內存,內存由jvm管理;如果我們的引用對象在堆中沒有引用指向他們時,當內存不足時,JVM會自動將這些對象進行回收釋放內存,這就是我們常說的垃圾回收。但垃圾回收沒有講述的這么簡單。
finalize()方法具有如下4個特點:
永遠不要主動調用某個對象的finalize()方法,該方法由垃圾回收機制自己調用;
finalize()何時被調用,是否被調用具有不確定性;
當JVM執行可恢復對象的finalize()可能會將此對象重新變為可達狀態;
當JVM執行finalize()方法時出現異常,垃圾回收機制不會報告異常,程序繼續執行。
public class FinalizeTest { private static FinalizeTest ft = null; public void info(){ System.out.println("測試資源清理得finalize方法"); } public static void main(String[] args) { //創建FinalizeTest對象立即進入可恢復狀態 new FinalizeTest(); //通知系統進行垃圾回收 System.gc(); //強制回收機制調用可恢復對象的finalize()方法 // Runtime.getRuntime().runFinalization(); System.runFinalization(); ft.info(); } @Override public void finalize(){ //讓ft引用到試圖回收的可恢復對象,即可恢復對象重新變成可達 ft = this; throw new RuntimeException("出異常了,你管不管啊"); } } //輸出結果 //測試資源清理得finalize方法
我們看到,finalize()方法將可恢復對象置為了可達對象,并且在finalize中拋出異常,都沒有任何信息,被忽略了。
1.8.1、對象在內存中的狀態
對象在內存中存在三種狀態:
可達狀態:有引用指向,這種對象為可達狀態;
可恢復狀態:失去引用,這種對象稱為可恢復狀態;垃圾回收機制開始回收時,回調用可恢復狀態對象的finalize()方法(如果此方法讓此對象重新獲得引用,就會變為可達狀態,否則,會變為不可大狀態)。
不可達狀態:徹底失去引用,這種狀態稱為不可達狀態,如果垃圾回收機制這時開始回收,就會將這種狀態的對象回收掉。
1.8.2、垃圾回收機制
垃圾回收機制只負責回收堆內存種的對象,不會回收任何物理資源(例如數據庫連接、網絡IO等資源);
程序無法精確控制垃圾回收的運行,垃圾回收只會在合適的時候進行。當對象為不可達狀態時,系統會在合適的時候回收它的內存。
在垃圾回收機制回收任何對象之前,總會先調用它的finalize()方法,該方法可能會將對象置為可達狀態,導致垃圾回收機制取消回收。
1.8.3、強制垃圾回收
上面我們已經說了,當對象失去引用時,會變為可恢復狀態,但垃圾回收機制什么時候運行,什么時候調用finalize方法無法知道。雖然垃圾回收機制無法精準控制,但java還是提供了方法可以建議JVM進行垃圾回收,至于是否回收,這取決于虛擬機。但似乎可以看到一些效果。
public class GcTest { public static void main(String[] args){ for(int i=0;i<4;i++){ //沒有引用指向這些對象,所以為可恢復狀態 new GcTest(); //強制JVM進行垃圾回收(這只是建議JVM) System.gc(); //Runtime.getRuntime().gc(); } } @Override public void finalize(){ System.out.println("系統正在清理GcTest資源。。。。"); } } //輸出結果 //系統正在清理GcTest資源。。。。 //系統正在清理GcTest資源。。。。
System.gc(),Runtime.getRuntime().gc()兩個方法作用一樣的,都是建議JVM垃圾回收,但不一定回收,多運行幾次,結果可能都不一致。
網站名稱:java中object方法有哪些
網頁URL:http://m.jcarcd.cn/article/cpghgj.html