众所周知,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.
方法总结:
方法详解:
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
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;否则的话,比较两个引用所指向的字符串长度是否一致,若一致,则通过逐个比较字符来判断两个引用所指向的对象是否相等。
/** 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
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;
}
/*@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}*/
@Override
public 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;
}
}
输出结果是:
firstUser
null
firstUser
firstUser
firstUser
null
public class User {
public static User gc_root = null;
@Override
protected 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");
}
}
}
输出结果是:
yes, i am alive
no, i am dead
在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();
}
}
}