Object類是一個(gè)特殊的類听系,是所有類的父類恳谎,是java中唯一沒(méi)有父類的類术浪,如果一個(gè)類沒(méi)有用extends明確指出繼承于某個(gè)類瓢对,那么它默認(rèn)繼承Object類。
1.獲取對(duì)象信息的方法:toString
public class Student {
private String name = "Mary";
private int age = 21;
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.toString());
}
}
輸出結(jié)果
com.netty.test23.Student@24367013
toString()方法添吗,它用于返回標(biāo)識(shí)對(duì)象值的字符串沥曹。類名@內(nèi)存地址
隨處可見(jiàn)toString()的主要原因是:只要對(duì)象與一個(gè)字符串通過(guò)操作符“+”連接起來(lái),java編譯器就會(huì)自動(dòng)地調(diào)用toString方法碟联,以便獲得這個(gè)對(duì)象的字符串描述妓美。
2.equals()
判斷兩個(gè)對(duì)象是否相等的方法±鸱酰可以先判斷地址是否相等壶栋,再判斷值是否相等。自定義對(duì)象需要重寫(xiě)該方法普监,不然該方法就是對(duì)比地址是否相等贵试。
public boolean equals(Object obj) {
return (this == obj);
}
- 以上方法,很明顯是對(duì)兩個(gè)對(duì)象的地址值進(jìn)行比較(即比較引用是否相同)凯正。
看下String中該方法的實(shí)現(xiàn)毙玻,先判斷地址是否相等,再判斷類型廊散、字符長(zhǎng)度桑滩,各個(gè)字符是否相等。
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;
}
- 很明顯允睹,這是進(jìn)行內(nèi)容的比較运准,而已經(jīng)不再是地址的比較。
3.hashCode()
返回當(dāng)前對(duì)象的hash code value缭受,這個(gè)類是用來(lái)支持一些hash table胁澳,例如HashMap。
它的性質(zhì)是:
- 在一個(gè)Java應(yīng)用的執(zhí)行期間米者,如果一個(gè)對(duì)象提供給equals做比較的信息沒(méi)有被修改的話韭畸,該對(duì)象多次調(diào)用hashCode()方法,該方法必須始終如一返回同一個(gè)Integer
- 如果兩個(gè)對(duì)象根據(jù)equals(Object)方法是相等的塘雳,那么調(diào)用二者各自的hashCode()方法必須產(chǎn)生同一個(gè)integer結(jié)果陆盘。
- 并不要求根據(jù)equals(lava.lang.Object)方法不相等的兩個(gè)對(duì)象,調(diào)用二者各自的hashCode()方法必須產(chǎn)生不同的integer結(jié)果
在集合查找時(shí)败明,hashcode能大大降低對(duì)象比較次數(shù),提高查找效率
Java對(duì)象的equals()和hashCode()是這樣規(guī)定的:
- 相等(相同)的對(duì)象必須具有相等的哈希碼(或者散列碼)太防。
解釋:想象一下妻顶,如果兩個(gè)java對(duì)象A和B酸员,A和B相等(equals結(jié)果為true),但A和B的哈希碼不同讳嘱,則A和B存入HashMap時(shí)的哈希碼計(jì)算得到的HashMap內(nèi)部數(shù)組位置索引可能不同幔嗦,那么A和B很有可能允許同時(shí)存入HashMap,顯然相等/相同的元素是不允許同時(shí)存入HashMap沥潭,HashMap不允許存放重復(fù)元素邀泉。 - 如果兩個(gè)對(duì)象的hashCode相同,他們并不一定相等钝鸽。
解釋:也就是說(shuō)汇恤,不同對(duì)象的HashCode可能相同;假如兩個(gè)Java對(duì)象A和B拔恰,A和B不相等(equals結(jié)果為false)因谎,但A和B的哈希碼相等,將A和B都存入HashMap時(shí)會(huì)發(fā)生哈希沖突颜懊,也就是A和B存入在HashMap時(shí)會(huì)發(fā)生哈希沖突财岔,也就是A和B存放在HashMap內(nèi)部數(shù)組的位置索引相同,這是HashMap會(huì)在該位置建立一個(gè)鏈表河爹,將A和B串起來(lái)放在該位置匠璧,顯然,該情況不違反HashMap的使用原則咸这,是允許的夷恍。當(dāng)然,hash沖突越少越好炊苫,盡量采用好的hash算法以避免哈希沖突裁厅。
所以,java對(duì)于equals方法和hashCode方法是這樣規(guī)定的:
1. 如果兩個(gè)對(duì)象相同侨艾,那么它們的hashCode值一定要相同执虹;
2. 如果兩個(gè)對(duì)象的hashCode相同,它們并一定相同(這里說(shuō)的對(duì)象相同指的是用equals方法比較)唠梨。
3. equals()相等的兩個(gè)對(duì)象袋励,hashCode()一定相等;equals()不相等的兩個(gè)對(duì)象当叭,卻并不能證明他們的hashCode()不相等茬故。
換句話說(shuō),equals()方法不相等的兩個(gè)對(duì)象蚁鳖,hashcode()有可能相等(我的理解是由于哈希碼在生成的時(shí)候產(chǎn)生沖突造成的)磺芭。反過(guò)來(lái),hashcode()不等醉箕,一定能推出equals()也不等钾腺;hashcode()相等徙垫,equals()可能相等,也可能不等放棒。
在object類中姻报,hashcode()方法是本地方法,返回的是對(duì)象的地址值间螟,而object類中的equals()方法比較的也是兩個(gè)對(duì)象的地址值吴旋,如果equals()相等,說(shuō)明兩個(gè)對(duì)象地址值也相等厢破,當(dāng)然hashcode()也就相等了荣瑟;在String類中,equals()返回的是兩個(gè)對(duì)象內(nèi)容的比較溉奕,當(dāng)兩個(gè)對(duì)象內(nèi)容相等時(shí)褂傀,Hashcode()方法根據(jù)String類的重寫(xiě)代碼的分析,也可知道hashcode()返回結(jié)果也會(huì)相等加勤。以此類推仙辟,可以知道Integer、Double等封裝類中經(jīng)過(guò)重寫(xiě)的equals()和hashcode()方法也同樣適合于這個(gè)原則鳄梅。當(dāng)然沒(méi)有經(jīng)過(guò)重寫(xiě)的類叠国,在繼承了object類的equals()和hashcode()方法后,也會(huì)遵守這個(gè)原則戴尸。
4. clone()
源碼
protected native Object clone() throws CloneNotSupportedException;
又源碼我們會(huì)發(fā)現(xiàn):
第一:Object類的clone()方法是一個(gè)native方法粟焊,native方法的效率一般來(lái)說(shuō)都是遠(yuǎn)高于java中的非native方法。這也解釋了為什么要用Object中的clone方法而不是先new一個(gè)類孙蒙,然后把原始對(duì)象中的信息復(fù)制到新對(duì)象中项棠,雖然這也實(shí)現(xiàn)了clone功能。
第二:Object類中的clone()方法被protected修飾符修飾挎峦。這也意味著如果要應(yīng)用clone()方法香追,必須繼承Object類,在java中所有的類是缺省繼承Object類的坦胶,也就不用關(guān)心這點(diǎn)了透典。然后重載clone()方法。這一點(diǎn)要考慮的是為了讓其他類能調(diào)用這個(gè)對(duì)象類的clone()方法顿苇,重載之后要把clone()方法的屬性設(shè)置為public.
第三:Object.clone()方法返回一個(gè)Object對(duì)象峭咒。我們必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換才能得到我們需要的類型。
淺層復(fù)制與深層復(fù)制
淺層復(fù)制:被復(fù)制的對(duì)象的所有成員屬性都與原來(lái)的對(duì)象值相同纪岁,而所有的對(duì)其他對(duì)象的引用仍然指向原來(lái)的對(duì)象凑队。換言之,淺層復(fù)制僅僅復(fù)制所考慮的對(duì)象幔翰,而不復(fù)制它引用的對(duì)象顽决。
- 當(dāng)某個(gè)類要復(fù)寫(xiě)clone方法時(shí)短条,要繼承Cloneable接口导匣。通常的克隆對(duì)象都是通過(guò)super.clone()方法來(lái)克隆對(duì)象才菠。
- 淺拷貝就是拷貝一個(gè)對(duì)象的副本。若只需要復(fù)制對(duì)象的字段值(對(duì)于基本數(shù)據(jù)類型贡定,如:int,long,float等赋访,則復(fù)制值;對(duì)于復(fù)合數(shù)據(jù)類型僅復(fù)制該字段值缓待,如數(shù)組變量則復(fù)制地址蚓耽,對(duì)于對(duì)象變量則復(fù)制對(duì)象reference).
public class ShadowClone implements Cloneable {
private int a;
private int[] b;
public Object clone() {
ShadowClone sc = null;
try {
sc = (ShadowClone) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sc;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}
public static void main(String[] args) {
ShadowClone c1 = new ShadowClone();
c1.setA(100);
c1.setB(new int[]{1000});
System.out.println("克隆前c1: a=" + c1.getA() + " b=" + c1.getB()[0]);
ShadowClone c2 = (ShadowClone) c1.clone();
c2.setA(50);
int[] a = c2.getB();
a[0] = 5;
c2.setB(a);
System.out.println("克隆前c1: a=" + c1.getA() + " b=" + c1.getB()[0]);
System.out.println("克隆后c2: a=" + c2.getA() + " b[0]=" + c2.getB()[0]);
}
}
結(jié)果為:
克隆前c1: a=100 b=1000
克隆前c1: a=100 b=5
克隆后c2: a=50 b[0]=5
c1和c2的對(duì)象模型:
- 可以看出,基本類型可以使用淺拷貝旋炒,而對(duì)于引用類型步悠,由于引用的是內(nèi)容相同,所以改變c2實(shí)例對(duì)象中的屬性就會(huì)影響到c1瘫镇。所以引用類型需要使用深拷貝鼎兽。另外,在開(kāi)發(fā)一個(gè)不可變類的時(shí)候铣除,如果這個(gè)不可變類中成員有引用類型谚咬,則就需要通過(guò)深拷貝來(lái)達(dá)到不可變的目的。
- 需要注意:
- 希望能實(shí)現(xiàn)clone功能的CloneClass類實(shí)現(xiàn)了Cloneable接口尚粘,這個(gè)接口屬于java.lang 包择卦,java.lang包已經(jīng)被缺省的導(dǎo)入類中,所以不需要寫(xiě)成java.lang.Cloneable郎嫁。
- 重載了clone() 方法秉继。最后在clone()方法中調(diào)用了super.clone(),這也意味著無(wú)論clone類的繼承結(jié)構(gòu)是什么樣的泽铛,super.clone()直接或間接調(diào)用了java.lang.Object類的clone()方法
- Object類的clone()是一個(gè)native方法尚辑,native方法的效率一般來(lái)說(shuō)都是遠(yuǎn)高于java中的非native方法。這也解釋了為什么要用Object中clone()方法而不是先new一個(gè)類厚宰,然后把原始對(duì)象中的信息賦到新對(duì)象中腌巾,雖然這也實(shí)現(xiàn)了clone方法。對(duì)于第二點(diǎn)铲觉,也要 觀察Object類中的clone()還是一個(gè)protected屬性的方法澈蝙。這也意味著如果要應(yīng)用clone()方法,必須繼承Object類撵幽,在 Java中所有的類是缺省繼承Object類的灯荧,也就不用關(guān)心這點(diǎn)了。然后重載clone()方法盐杂。還有一點(diǎn)要考慮的是為了讓其它類能調(diào)用這個(gè)clone 類的clone()方法逗载,重載之后要把clone()方法的屬性設(shè)置為public哆窿。
深層復(fù)制:被復(fù)制對(duì)象的所有變量都含有與原來(lái)的對(duì)象相同的值,除去那些引用其他對(duì)象的變量厉斟。那么引用其他對(duì)象的變量將指向被復(fù)制過(guò)的新對(duì)象挚躯,而不是原有的那些被引用的對(duì)象。換言之擦秽,深層復(fù)制要復(fù)制的對(duì)象引用的對(duì)象都復(fù)制一遍码荔。
public class Student implements Cloneable {
private String name;
private int age;
Professor pro;
public Student(){}
public Student(String name,int age,Professor pro){
this.name=name;
this.age=age;
this.pro=pro;
}
public Object clone(){
Student o=null;
try {
//Object中的clone()識(shí)別出你要復(fù)制的是哪一個(gè)對(duì)象。
o=(Student)super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
o.pro=(Professor)pro.clone();
return o;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Professor getPro() {
return pro;
}
public void setPro(Professor pro) {
this.pro = pro;
}
}
class Professor implements Cloneable{
private String name;
private int age;
public Professor(){}
public Professor(String name,int age){
this.name=name;
this.age=age;
}
public Object clone(){
Object o=null;
try {
o=super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}