序言
1、Object 类位于 java.lang 包中,是所有 Java 类的祖先,Java 中的每个类都由它扩展而来。
2、定义Java类时如果没有显示的指明父类,那么就默认继承了 Object 类。
例如:
public class Demo{
// ...
}
实际上是下面代码的简写形式:
public class Demo extends Object{
// ...
}
在Java中,只有基本类型不是对象,例如数值、字符和布尔型的值都不是对象,所有的数组类型,不管是对象数组还是基本类型数组都是继承自 Object 类。
Object类
Object 类定义了一些有用的方法,由于是根类,这些方法在其他类中都存在,一般是进行了重载或覆盖,实现了各自的具体功能。
1、equals() 方法
Object 类中的 equals() 方法用来检测一个对象是否等价于另外一个对象,语法为:
public boolean equals(Object obj)
例如:
obj1.equals(obj2);
在Java中,数据等价的基本含义是指两个数据的值相等。
在通过 equals() 和“==”进行比较的时候,
(1)引用类型数据比较的是引用,即内存地址,
(2)基本数据类型比较的是值。
注意:
(1)equals()方法只能比较引用类型,“==”可以比较引用类型及基本类型。
(2)当用 equals() 方法进行比较时,对类 File、String、Date及包装类来说,是比较类型及内容而不考虑引用的是否是同一个实例
· 用“==”进行比较时,符号两边的数据类型必须一致(可自动转换的数据类型除外),否则编译出错,而用 equals 方法比较的两个数据只要都是引用类型即可。
public class ArrayTest {
public static void main(String[] args){
String a = new String("aw");
String b = new String("aw");
System.out.println(a==b);//false
}
}
解读:
“== ”用于判断的是对象的内存地址。显然,尽管 a 与 b 对象的值相同,但是在内存中的地址是不同的,即两个对象是不一样的。
2、hashCode() 方法
散列码(hashCode)是按照一定的算法由对象得到的一个数值,散列码没有规律。如果 x 和 y 是不同的对象,x.hashCode() 与 y.hashCode() 基本上不会相同。
hashCode()方法主要用来在集合中实现快速查找等操作,也可以用于对象的比较。
注意:
(1)Object默认的hashCode()方法实际上返回的就是对象的存储地址。
(2)所以如果某个类(如User类)没有重写hashCode()方法,其存储于HashMap集合中时,计算出来的hash值是以对象的内存地址(而不是你期望的Id)为底数的。因此需要根据自身程序需求重写该方法。
(3)在 Java 规范里面规定,一般是覆盖 equals() 方法应该连带覆盖 hashCode() 方法。
在 Java 中,对 hashCode 的规定如下:
· 在同一个应用程序执行期间,对同一个对象调用 hashCode(),必须返回相同的整数结果——前提是 equals() 所比较的信息都不曾被改动过。至于同一个应用程序在不同执行期所得的调用结果,无需一致。
如果两个对象被 equals() 方法视为相等,那么对这两个对象调用 hashCode() 必须获得相同的整数结果。
· 如果两个对象被 equals() 方法视为不相等,那么对这两个对象调用 hashCode() 不必产生不同的整数结果。然而程序员应该意识到,对不同对象产生不同的整数结果,有可能提升hashTable(后面会学到,集合框架中的一个类)的效率。
简单地说:
(1)如果两个对象相同,那么它们的 hashCode 值一定要相同;
(2)如果两个对象的 hashCode 值相同,它们并不一定相同。
public class ArrayTest {
public static void main(String[] args){
String a = new String("aw");
String b = new String("aw");
String c= "aa";
String d= "aa";
System.out.println(a.hashCode());
System.out.println(b.hashCode());
System.out.println(c.hashCode());
System.out.println(d.hashCode());
System.out.println(a==b);//false
System.out.println(c==d);//true
}
}
运行结果为:
3126
3126
3104
3104
false
true
注意:
(1)在String类中,对HashCode()进行重写后,hashCode()方法用于返回字符串的哈希码。此时不是指内存地址了,不要理解偏了。
(2)也因此,往HashMap集合中存放Key类型为String的数据时,不用特意重写String的equals()和hashcode()函数!
其哈希码根据以下公式计算:
s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
s[i]为字符,n为字符数组长度
对应源码为:
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;
}
3、toString() 方法
toString() 方法是 Object 类中定义的另一个重要方法,是对象的字符串表现形式,语法为:
public String toString()
返回值是String类型,用于描述当前对象的有关信息。Object 类中实现的 toString() 方法是返回当前对象的类型和内存地址信息,但在一些子类(如String、Date等)中进行了重写,也可以根据需要在用户自定义类型中重写 toString() 方法,以返回更适用的信息。
除显式调用对象的 toString() 方法外,在进行String与其它类型数据的连接操作时,会自动调用 toString() 方法。
4、clone()方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
(1)当我们使用“=”将一个引用类型赋值给另一个引用类型的时候,会使得两个引用类型共享一份资源的拷贝。这时候修改一个引用会使的两个引用的内容都发生变化。
(2)使用clone()就可以解决上面这个问题。Clone之后的两个引用类型各自有自己的一份内容,不会相互影响。
当有一个对象
Person p1=new Person("name",age);
Person p2=p1;
此时只是简单的copy了一下引用,p1和p2都指向了内存中的同一个对象,修改p1和p2中任何一个都会影响到对方。
当函数的参数是对象时,传递的是引用类型,在对象函数内部的操作会影响到外边对象。如果希望传递的是对象的一个拷贝时,这时就需要用到object的clone()方法。)
拷贝对象返回的是一个新对象,而不是引用;拷贝对象与new操作符返回的新对象的区别就是这个拷贝对象已经包含了原来对象的一些信息,而不是对象的初始信息。
在clone中分为浅拷贝和深拷贝,浅拷贝就是不管参数是基本类型还是引用类型,直接调用super.clone()进行拷贝,结果引用类型还是指向相同的内存;深拷贝就是在调用super.clone()之后,还要调用引用参数的clone()函数,进行拷贝,来达到深拷贝。
什么时候使用浅拷贝,什么时候使用深拷贝:
如果是基本类型使用浅拷贝,如果是引用变量使用深拷贝。
实现克隆的步骤:
(1)实现Cloneable接口,Cloneable没有任何抽象方法,称为标志接口。
(2)覆盖基类 的clone方法,并声明为public
(3)在派生类中重写clone方法,并调用super.clone()
浅拷贝:
(1)基本的数据类型(int、String等)是会拷贝复制一份新的对象
(2)引用对象(自定义类的对象)还是引用同一份对象。
浅拷贝示例代码:
public class Info {
public int idnumber;
public int getIdnumber() {
return idnumber;
}
public void setIdnumber(int idnumber) {
this.idnumber = idnumber;
}
}
public class Person implements Cloneable{
private int age;
private String nameString;
//引用对象
public Info info;
public Info getInfo() {
return info;
}
public void setInfo(Info info) {
this.info = info;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getNameString() {
return nameString;
}
public void setNameString(String nameString) {
this.nameString = nameString;
}
public String toString()
{
return "name:"+nameString+",age:"+age+",idnumber"+info.idnumber;
}
//重写Object的Clone()方法
public Object clone()
{
Person person=null;
try {
//浅拷贝
person=(Person)super.clone();
//若加上下面这一句,便成为深拷贝
//person.info=(Info)info.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
}
public class Hello {
public static void main(String[] args) {
//定义Person对象1
Info info=new Info();
info.setIdnumber(100);
Person person1=new Person();
person1.setAge(10);
person1.setNameString("tiantian");
person1.info=info;
//克隆person1给person2,然后修改person2的所有数据,最后比较二者数据
Person person2=(Person)person1.clone();
person2.setNameString("hello");
person2.setAge(20);
person2.info.setIdnumber(200);
System.out.println("person2"+person2);
System.out.println("person1"+person1);
}
}
最后的结果是:
person2name:hello,age:20,idnumber200
person1name:tiantian,age:10,idnumber200
person1的基本类型name和age都没被改变,但引用类型的idnumber数值改变了,说明对于idnumber(引用对象info的属性),浅拷贝达不到完整拷贝的目的。
深拷贝代码示例:
public class Info implements Cloneable {//Info也进行拷贝
public int idnumber;
public int getIdnumber() {
return idnumber;
}
public void setIdnumber(int idnumber) {
this.idnumber = idnumber;
}
public Object clone()
{
Info info=null;
try {
info=(Info)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return info;
}
}
//实现Cloneable
public class Person implements Cloneable{
private int age;
private String nameString;
public Info info;
public Info getInfo() {
return info;
}
public void setInfo(Info info) {
this.info = info;
}
public Person() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getNameString() {
return nameString;
}
public void setNameString(String nameString) {
this.nameString = nameString;
}
public String toString()
{
return "name:"+nameString+",age:"+age+",idnumber"+info.idnumber;
}
public Object clone()
{
Person person=null;
try {
//浅拷贝
person=(Person)super.clone();
//加上这一句就变成了深拷贝,对对象引用也进行一次拷贝
person.info=(Info)info.clone();
} catch (CloneNotSupportedException e)
e.printStackTrace();
}
return person;
}
}
结果:
person2name:hello,age:20,idnumber200
person1name:tiantian,age:10,idnumber100
person2对象的数据进行更改,已经丝毫不会影响person1对象的任何数据了。
5、getClass()方法
final方法,获得运行时类型。
6、finalize()方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
7、wait()方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。
wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生:
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
8、notify()方法
该方法唤醒在该对象上等待的某个线程。
9、notifyAll()方法
该方法唤醒在该对象上等待的所有线程。
总结
Object类中的getClass(),notify(),notifyAll(),wait()等方法被定义为final类型,因此不能重写。
以上几种方法,在Java中是经常用到的,这里仅作简单介绍,让大家对Object类和其他类有所了解,详细说明请参考 Java API 文档。