Object类源码学习

来源:互联网 发布:在绿城工作怎么样知乎 编辑:程序博客网 时间:2024/04/30 12:54

众所周知,Java中所有的类都直接或间接继承自Object类,既然它如此重要,那么就有必要了解它的实现原理,这样也能够使得我们更加准确的使用。下面的一句话来自于J2SE 文档:

  • Class Object is the root of the class hierarchy. Every class hasObject as a superclass. All objects, including arrays, implement the methods of this class.

方法总结:



方法详解:

(1)equals和hashCode方法:我们可能会经常看到书上或者是文章中说自定义类时,如果重写equals方法就必须重写hashCode方法,而且要保证两者的一致性、为什么需要这样做呢?我们首先看一下Object中这两个方法的实现:
 public boolean equals(Object obj) {        return (this == obj); }
<pre name="code" class="java">public native int hashCode();
equals方法中直接通过this == obj来判定两个对象是否相等,其中==表示的是this与obj引用指向同一个对象。hashCode中native表明这个是本地方法,即它是通过C语言实现的。但由于在实际的编码中可能并不强求两个引用指向同一个对象是equals才返回true,以String类为例:
public class Test {public static void main(String[] args) {String str1 = "abc";String str2 = new String("abc");System.out.println(str1.equals(str2));}}
输出结果为:true
在上面的代码中str1指向的是常量池中的“abc”字符串对象位于虚拟机的方法区中,而str2指向的是虚拟机堆空间中的字符串对象,二者并没有指向同一个对象但equals方法返回的却是true,也就是说String类重写了Object的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类的equals方法首先比较两个引用是否指向同一个对象,若指向同一个对象则返回true;否则的话,比较两个引用所指向的字符串长度是否一致,若一致,则通过逐个比较字符来判断两个引用所指向的对象是否相等。

现在我们大致了解了equals的实现以及其作用,那么hashCode的作用又是什么呢?
相信大家对查找都比较了解,那么不知道是否了解哈希算法,通过为每个对象生成一个哈希值,按照哈希值来存储对象,当要查找对象时,则首先获取对象的哈希值,然后根据哈希值进行查找,这能大大加快查找速度。那么问题来了,hashCode函数又与equals函数有什么关系呢?为什么要保持两者的一致性呢?我们来看看String类中hashCode的实现:
 /** The value is used for character storage. */    private final char value[];    /** Cache the hash code for the string */    private int hash; // Default to 0
public int hashCode() {        int h = hash;        if (h == 0 && value.length > 0) {            char val[] = value;            for (int i = 0; i < value.length; i++) {                h = 31 * h + val[i];            }            hash = h;        }        return h;    }
可以看出,得到一个字符串的哈希值是通过这个计算公式h = 31 * h + val[i]得到的,再回想一下String的equals方法的实现:如两个字符串对应位置字符相同则返回true。可以得出若两个字符串相等,那么它们的哈希值也是相同的。通过如下代码检验是否正确:
public class Test {public static void main(String[] args) {String str1 = "abc";String str2 = new String("abc");System.out.println("str1.hashCode() = " + str1.hashCode() + " str2.hashCode() = " + str2.hashCode());}}
输出结果:str1.hashCode() = 96354 str2.hashCode() = 96354

还有一个问题就是为什么要保证hashCode方法与equals方法实现上的一致性呢?
import java.util.HashMap;import java.util.Map;public class Test {public static void main(String[] args) {Map<User, String> map = new HashMap<User, String>();User firstUser = new User("zcj", 25);map.put(firstUser, "firstUser");User targetUser = new User("zcj", 25);System.out.println(targetUser.equals(firstUser));String result = map.get(firstUser);System.out.println(result);result = map.get(targetUser);System.out.println(result);}}class User {private String name;private int age;public User(String name, int age) {super();this.name = name;this.age = age;}/*@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + age;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}*/@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;User other = (User) obj;if (age != other.age)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}}
输出结果是:
true
firstUser
null
分析一下:在User类的定义中只是重写了equals方法而没有重写hashCode方法,也就是说hashCode方法继承自Object类,在Object类hashCode实现中哈希值就是对象的地址故只有同一个对象才能返回相同的哈希值,在上面的例子中,firstUser和targetUser相等但是由于指向不同的User对象而返回不同的哈希值,故通过targetUser在map中查找firstUser是无法实现的,这会给我们的编码带来极大问题,添加进去的项无法被查找到。这是由于在HashMap的get方法的查找中首先通过对象的哈希值来查找到对象所在位置,当然这个位置可能保存的是一个链表即存在不止一个对象的哈希值是相同的,那么接下来我们应该怎么查找到目标对象呢?显然是通过equals方法来逐个比对,一旦发现返回true则返回该对象,代表查找成功,这也是为什么要是的hashCode方法与equals方法一致的原因,否则的话出现则插入的对象无法查找到的现象。
将上面代码中hashCode方法的注释去掉,则输出结果是:
true
firstUser
firstUser

如果只是重写了hashCode方法而没有重写equals方法,输出如下:
false
firstUser
null
相信这个结果应当不难理解。根据哈希值可以定位但由于equals没有重写,即继承自Object的方法要求两个引用指向同一个对象时方返回true,故无法通过equals找到相等的对象,返回null。

(2)finalize方法:一个对象要被垃圾回收需要经过两次标记过程,第一次标记之后会将这些对象(重写了finalize方法并且finalize方法没有被调用过)放入一个低优先级的队列中,接受调度时可以通过执行重写的finalize方法来重新关联上GC_Roots,以逃脱垃圾回收。
public class User {public static User gc_root = null;@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize method executed!");gc_root = this;}public void isAlive() {System.out.println("yes, i am alive");}public static void main(String[] args) {gc_root = new User();gc_root = null;System.gc();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if(gc_root != null) {gc_root.isAlive();} else {System.out.println("no, i am dead");}gc_root = null;System.gc();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if(gc_root != null) {gc_root.isAlive();} else {System.out.println("no, i am dead");}}}
输出结果是:
finalize method executed!
yes, i am alive
no, i am dead

也就是所finalize方法能够使得对象逃脱一次垃圾回收。关于其中具体细节,可以参考深入理解Java虚拟机。

(3)clone方法:有时我们希望能够得到对象的一个副本而不是指向同一个对象的引用,这时就需要调用clone方法,在重写clone方法时首先要调用父类的clone方法,关于clone方法最重要的就是浅复制和深复制。

  在Java语言中,当clone方法被对象调用,就会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。那么在java语言中,有几种方式可以创建对象呢?

  1.使用new操作符创建一个对象

  2.使用clone方法复制一个对象

  那么这两种方式有什么相同和不同呢?

  new操作符的本意是分配内存。程序执行到new操作符时,首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。

  clone在第一步是和new相似的,都需要分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用源对象中对应的各个域,填充新对象的域,填充完成之后,clone方法返回一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

  下面让我们来关注一下Cloneable接口,其与Object类中的clone方法是否存在联系呢?让我们看看API中的说明:


注意:Cloneable接口中并没有定义clone方法,但它决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常。

拷贝对象往往会导致创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构。这个过程中没有调用构造器。在这个类中声明的域将等同于被克隆对象中的域。如果每个域包含一个基本类型的值,或者包含一个指向不可变对象的引用,那么返回的对象则可能正是你所需要的对象,在这种情况下不需要再做进一步处理。

  深拷贝 or 浅拷贝

public class User implements Cloneable {
private String userName;
private String password;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "User [userName=" + userName + ", password=" + password + "]";
}

public static void main(String[] args) {
User user = new User();
user.setUserName("zcj");
user.setPassword("123456");
try {
User cloneUser = (User)user.clone();
System.out.println(user.userName == cloneUser.userName);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

上述代码的输出结果是:true;也就验证了Object中的clone方法实现的是浅拷贝,即只是让被复制对象与复制对象的的值相同而并没有真正的实现对对象实例属性的clone,两者的实例属性依然是指向同一个对象。那么要想实现深拷贝,就理应重写clone方法。除了调用父类中的clone方法得到新的对象, 还要将该类中的引用变量也clone出来, 这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。


(4)wait和notify,notifyAll方法:这类方法都是与对象锁有关,具体细节,后续补充。








0 0