java.lang包—对象基类Object
原文作者:Boblim
原文地址:Java:Object類詳解
目錄
一、上帝類
二、Object的類方法
三、常見面試題
Java的一些特性會讓初學(xué)者感到困惑,但在有經(jīng)驗(yàn)的開發(fā)者眼中,卻是合情合理的。例如,新手可能不會理解Object類。這篇文章分成三個部分講跟Object類及其方法有關(guān)的問題。
一、上帝類
什么是Object類?
Object類存儲在java.lang包中,是所有java類(Object類除外)的終極父類。當(dāng)然,數(shù)組也繼承了Object類。然而,接口是不繼承Object類的,原因在這里指出:Section 9.6.3.4 of the Java Language Specification
?Object類中聲明了以下函數(shù),java的任何類都繼承了這些函數(shù),并且可以覆蓋不被final修飾的函數(shù)。例如,沒有final修飾的toString()函數(shù)可以被覆蓋,但是final wait()函數(shù)就不行。我會在下文中詳細(xì)說明這些函數(shù)。
protected Object clone() boolean equals(Object obj) protected void finalize() Class< > getClass() int hashCode() String toString() void wait() void wait(long timeout) void wait(long timeout, int nanos) void notify() void notifyAll()可以聲明要“繼承Object類”嗎?
可以。Object 類可以顯示繼承,也可以隱式繼承;在代碼中明確地寫出繼承Object類沒有語法錯誤。即以下兩種寫法都正確:
importjava.lang.Object; //明確的繼承Object類 public class Employee extends Object {public static void main(String[] args) {} } //默認(rèn)繼承Object類 public class Employee{private String name;publicstaticvoidmain(String[] args){} }二、Object的類方法
1、關(guān)于克隆函數(shù)clone()
Java基礎(chǔ)—復(fù)制之深拷貝與淺拷貝
2、關(guān)于wait()/notify()/?notifyAll()
Java并發(fā)編程—線程間協(xié)作方式wait()/notify()/notifyAll()原理分析
3、關(guān)于比較函數(shù)equals(Object obj)
什么時(shí)候需要重寫equals()?我們知道每一個java類都繼承自O(shè)bject類,equals()是Object類中提供的方法之一。那么,讓我們先來看看Object#equals()在Java中的原代碼:
public boolean equals(Object obj) {return (this == obj); }可以看出,只有當(dāng)一個實(shí)例等于它本身的時(shí)候,equals()才會返回true值。通俗地說,此時(shí)比較的是兩個引用是否指向內(nèi)存中的同一個對象,也可以稱做是否實(shí)例相等。而我們在使用equals()來比較兩個指向值對象的引用的時(shí)候,往往希望知道它們邏輯上是否相等,而不是它們是否指向同一個對象。在這樣的情況下, 如果超類也沒有重寫equals()以實(shí)現(xiàn)期望的行為,這時(shí)我們就需要重寫equals方法。而且這樣做也使得這個類的實(shí)例可以被用做映射表(map)的鍵,或者集合(set)的元素,并使映射表或者集合表現(xiàn)出預(yù)期的行為。Object類提供的equals方法只是一個很簡單的,不能適應(yīng)應(yīng)用程序有特殊要求的情況。比如網(wǎng)絡(luò)對象,帶有volatile屬性的對象,或是帶有多層子對象的復(fù)合對象,等等,是不能像String一類的對象進(jìn)行簡單比較的,所以提供了這樣一個機(jī)制,就像serializable接口一樣,既有默認(rèn)的序列化方法,也提供了程序自己定制,覆蓋默認(rèn)方式的可能性。Object僅僅提供了一個對引用的比較,如果兩個引用不是同一個那就返回false,這是無法滿足大多數(shù)對象比較的需要的,所以要覆蓋。
怎樣重寫equals()方法?重寫equals()方法看起來非常簡單,但是有許多改寫的方式會導(dǎo)致錯誤,并且后果非常嚴(yán)重。要想正確改寫equals()方法,你必須要遵守它的通用約定。下面是約定的內(nèi)容,來自java.lang.Object的規(guī)范:
equals方法實(shí)現(xiàn)了等價(jià)關(guān)系(equivalence relation):
- 1. 自反性:對于任意的引用值x,x.equals(x)一定為true。
- 2. 對稱性:對于任意的引用值x 和 y,當(dāng)x.equals(y)返回true時(shí),y.equals(x)也一定返回true。
- 3. 傳遞性:對于任意的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也一定返回true。
- 4. 一致性:對于任意的引用值x 和 y,如果用于equals比較的對象信息沒有被修改,多次調(diào)用x.equals(y)要么一致地返回true,要么一致地返回false。
- 5. 非空性:對于任意的非空引用值x,x.equals(null)一定返回false。
接下來我們通過實(shí)例來理解上面的約定。我們首先以一個簡單的非可變的二維點(diǎn)類作為開始:
public class Point{private final int x;private final int y;public Point(int x, int y){this.x = x;this.y = y;}public boolean equals(Object o){if(!(o instanceof Point))return false;Point p = (Point)o;return p.x == x && p.y == y;}}假設(shè)你想要擴(kuò)展這個類,為一個點(diǎn)增加顏色信息:
public class ColorPoint extends Point{private Color color;public ColorPoint(int x, int y, Color color){super(x, y);this.color = color;}//override equasl()public boolean equals(Object o){if(!(o instanceof ColorPoint))return false;ColorPoint cp = (ColorPoint)o;return super.equals(o) && cp.color==color;} }我們重寫了equals方法,只有當(dāng)實(shí)參是另一個有色點(diǎn),并且具有同樣的位置和顏色的時(shí)候,它才返回true。可這個方法的問題在于,你在比較一個普通點(diǎn)和一個有色點(diǎn),以及反過來的情形的時(shí)候,可能會得到不同的結(jié)果:
public static void main(String[] args){Point p = new Point(1, 2);ColorPoint cp = new ColorPoint(1, 2, Color.RED);System.out.println(p.equals(cp));System.out.println(cp.eqauls(p)); }運(yùn)行結(jié)果:
true??
false
這樣的結(jié)果顯然違反了對稱性,你可以做這樣的嘗試來修正這個問題:讓ColorPoint.equals在進(jìn)行“混合比較”的時(shí)候忽略顏色信息:
public boolean equals(Object o){if(!(o instanceof Point))return false;//如果o是一個普通點(diǎn),就忽略顏色信息if(!(o instanceof ColorPoint))return o.equals(this);//如果o是一個有色點(diǎn),就做完整的比較ColorPoint cp = (ColorPoint)o;return super.equals(o) && cp.color==color; }這種方法的結(jié)果會怎樣呢?讓我們先來測試一下:
public static void main(String[] args){ColorPoint p1 = new ColorPoint(1, 2, Color.RED);Point p2 = new Point(1, 2);ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);System.out.println(p1.equals(p2));System.out.println(p2.equals(p1));System.out.println(p2.equals(p3));System.out.println(p1.eqauls(p3)); }運(yùn)行結(jié)果:
true
true
true
false
這種方法確實(shí)提供了對稱性,但是卻犧牲了傳遞性(按照約定,p1.equals(p2)和p2.eqauals(p3)都返回true,p1.equals(p3)也應(yīng)返回true)。要怎么解決呢?事實(shí)上,這是面向?qū)ο笳Z言中關(guān)于等價(jià)關(guān)系的一個基本問題。要想在擴(kuò)展一個可實(shí)例化的類的同時(shí),既要增加新的特征,同時(shí)還要保留equals約定,沒有一個簡單的辦法可以做到這一點(diǎn)。新的解決辦法就是不再讓ColorPoint擴(kuò)展Point,而是在ColorPoint中加入一個私有的Point域,以及一個公有的視圖(view)方法:
public class ColorPoint{private Point point;private Color color;public ColorPoint(int x, int y, Color color){point = new Point(x, y);this.color = color;}//返回一個與該有色點(diǎn)在同一位置上的普通Point對象public Point asPoint(){return point;}public boolean equals(Object o){if(o == this)return true;if(!(o instanceof ColorPoint))return false;ColorPoint cp = (ColorPoint)o;return cp.point.equals(point)&&cp.color.equals(color);} }還有另外一個解決的辦法就是把Point設(shè)計(jì)成一個抽象的類(abstract class),這樣你就可以在該抽象類的子類中增加新的特征,而不會違反equals約定。因?yàn)槌橄箢悷o法創(chuàng)建類的實(shí)例,那么前面所述的種種問題都不會發(fā)生。
重寫equals方法的要點(diǎn):
- 1. 使用==操作符檢查“實(shí)參是否為指向?qū)ο蟮囊粋€引用”。
- 2. 使用instanceof操作符檢查“實(shí)參是否為正確的類型”。
- 3. 把實(shí)參轉(zhuǎn)換到正確的類型。
- 4. 對于該類中每一個“關(guān)鍵”域,檢查實(shí)參中的域與當(dāng)前對象中對應(yīng)的域值是否匹配。對于既不是float也不是double類型的基本類型的域,可以使用==操作符進(jìn)行比較;對于對象引用類型的域,可以遞歸地調(diào)用所引用的對象的equals方法;對于float類型的域,先使用Float.floatToIntBits轉(zhuǎn)換成int類型的值,然后使用==操作符比較int類型的值;對于double類型的域,先使用Double.doubleToLongBits轉(zhuǎn)換成long類型的值,然后使用==操作符比較long類型的值。
- 5. 當(dāng)你編寫完成了equals方法之后,應(yīng)該問自己三個問題:它是否是對稱的、傳遞的、一致的?(其他兩個特性通常會自行滿足)如果答案是否定的,那么請找到這些特性未能滿足的原因,再修改equals方法的代碼。
三、常見面試題
1、寫出java.lang.Object類的六個常用方法?Object類中的各個方法有什么作用?
這是一個非常常見的問題,用來確定你對基礎(chǔ)知識的熟悉程度。
2、
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的java.lang包—对象基类Object的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java.lang包—类加载器Class
- 下一篇: java.lang包—基本类型的封装类