Object类是一个特殊的类,是所有类的父类,是java中唯一没有父类的类,如果一个类没有用extends明确指出继承于某个类,那么它默认继承Object类。
1.获取对象信息的方法: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());
}
}
输出结果
com.netty.test23.Student@24367013
toString()方法,它用于返回标识对象值的字符串。类名@内存地址
随处可见toString()的主要原因是:只要对象与一个字符串通过操作符“+”连接起来,java编译器就会自动地调用toString方法,以便获得这个对象的字符串描述。
2.equals()
判断两个对象是否相等的方法。可以先判断地址是否相等,再判断值是否相等。自定义对象需要重写该方法,不然该方法就是对比地址是否相等。
public boolean equals(Object obj) {
return (this == obj);
}
- 以上方法,很明显是对两个对象的地址值进行比较(即比较引用是否相同)。
看下String中该方法的实现,先判断地址是否相等,再判断类型、字符长度,各个字符是否相等。
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;
}
- 很明显,这是进行内容的比较,而已经不再是地址的比较。
3.hashCode()
返回当前对象的hash code value,这个类是用来支持一些hash table,例如HashMap。
它的性质是:
- 在一个Java应用的执行期间,如果一个对象提供给equals做比较的信息没有被修改的话,该对象多次调用hashCode()方法,该方法必须始终如一返回同一个Integer
- 如果两个对象根据equals(Object)方法是相等的,那么调用二者各自的hashCode()方法必须产生同一个integer结果。
- 并不要求根据equals(lava.lang.Object)方法不相等的两个对象,调用二者各自的hashCode()方法必须产生不同的integer结果
在集合查找时,hashcode能大大降低对象比较次数,提高查找效率
Java对象的equals()和hashCode()是这样规定的:
- 相等(相同)的对象必须具有相等的哈希码(或者散列码)。
解释:想象一下,如果两个java对象A和B,A和B相等(equals结果为true),但A和B的哈希码不同,则A和B存入HashMap时的哈希码计算得到的HashMap内部数组位置索引可能不同,那么A和B很有可能允许同时存入HashMap,显然相等/相同的元素是不允许同时存入HashMap,HashMap不允许存放重复元素。 - 如果两个对象的hashCode相同,他们并不一定相等。
解释:也就是说,不同对象的HashCode可能相同;假如两个Java对象A和B,A和B不相等(equals结果为false),但A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存入在HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同,这是HashMap会在该位置建立一个链表,将A和B串起来放在该位置,显然,该情况不违反HashMap的使用原则,是允许的。当然,hash冲突越少越好,尽量采用好的hash算法以避免哈希冲突。
所以,java对于equals方法和hashCode方法是这样规定的:
1. 如果两个对象相同,那么它们的hashCode值一定要相同;
2. 如果两个对象的hashCode相同,它们并一定相同(这里说的对象相同指的是用equals方法比较)。
3. equals()相等的两个对象,hashCode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashCode()不相等。
换句话说,equals()方法不相等的两个对象,hashcode()有可能相等(我的理解是由于哈希码在生成的时候产生冲突造成的)。反过来,hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。
在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然hashcode()也就相等了;在String类中,equals()返回的是两个对象内容的比较,当两个对象内容相等时,Hashcode()方法根据String类的重写代码的分析,也可知道hashcode()返回结果也会相等。以此类推,可以知道Integer、Double等封装类中经过重写的equals()和hashcode()方法也同样适合于这个原则。当然没有经过重写的类,在继承了object类的equals()和hashcode()方法后,也会遵守这个原则。
4. clone()
源码
protected native Object clone() throws CloneNotSupportedException;
又源码我们会发现:
第一:Object类的clone()方法是一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中的clone方法而不是先new一个类,然后把原始对象中的信息复制到新对象中,虽然这也实现了clone功能。
第二:Object类中的clone()方法被protected修饰符修饰。这也意味着如果要应用clone()方法,必须继承Object类,在java中所有的类是缺省继承Object类的,也就不用关心这点了。然后重载clone()方法。这一点要考虑的是为了让其他类能调用这个对象类的clone()方法,重载之后要把clone()方法的属性设置为public.
第三:Object.clone()方法返回一个Object对象。我们必须进行强制类型转换才能得到我们需要的类型。
浅层复制与深层复制
浅层复制:被复制的对象的所有成员属性都与原来的对象值相同,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅层复制仅仅复制所考虑的对象,而不复制它引用的对象。
- 当某个类要复写clone方法时,要继承Cloneable接口。通常的克隆对象都是通过super.clone()方法来克隆对象。
- 浅拷贝就是拷贝一个对象的副本。若只需要复制对象的字段值(对于基本数据类型,如:int,long,float等,则复制值;对于复合数据类型仅复制该字段值,如数组变量则复制地址,对于对象变量则复制对象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]);
}
}
结果为:
克隆前c1: a=100 b=1000
克隆前c1: a=100 b=5
克隆后c2: a=50 b[0]=5
c1和c2的对象模型:
- 可以看出,基本类型可以使用浅拷贝,而对于引用类型,由于引用的是内容相同,所以改变c2实例对象中的属性就会影响到c1。所以引用类型需要使用深拷贝。另外,在开发一个不可变类的时候,如果这个不可变类中成员有引用类型,则就需要通过深拷贝来达到不可变的目的。
- 需要注意:
- 希望能实现clone功能的CloneClass类实现了Cloneable接口,这个接口属于java.lang 包,java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。
- 重载了clone() 方法。最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法
- Object类的clone()是一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone方法。对于第二点,也要 观察Object类中的clone()还是一个protected属性的方法。这也意味着如果要应用clone()方法,必须继承Object类,在 Java中所有的类是缺省继承Object类的,也就不用关心这点了。然后重载clone()方法。还有一点要考虑的是为了让其它类能调用这个clone 类的clone()方法,重载之后要把clone()方法的属性设置为public。
深层复制:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那么引用其他对象的变量将指向被复制过的新对象,而不是原有的那些被引用的对象。换言之,深层复制要复制的对象引用的对象都复制一遍。
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()识别出你要复制的是哪一个对象。
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;
}
}