[ 转载 ] Java基础10--关于Object类下所有方法的简单解析

时间:2022-12-23 13:04:15

关于Object类下所有方法的简单解析

类Object是类层次结构的根类,是每一个类的父类,所有的对象包括数组,String,Integer等包装类,所以了解Object是很有必要的,话不多说,我们直接来看jdk的源码,开始我们的分析之路

1.hashcode()

public native int hashCode();//native说明跟机器有关,跟对象的地址有关

如果我们新建一个类,而hashcode没有被重写的话,那么hashcode返回的值只于对象的地址有关,如果hashcode被重写了,那么就另当别论了,但是如果我们重写了equals()的话,那么hashcode(),一定要重写,至于为什么要重写的话,请参考我这篇博客:http://www.cnblogs.com/yinbiao/p/8110196.html

现在我们来看一下JDk API文档里面关于hashcode的说明

hashCode 的常规协定是:

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

2.equals()

public boolean equals(Object obj) {
        return (this == obj);
    }

可以看到如果equals()如果没有被重写的话,比较的是对象的地址,String,Integer等包装类里面都重写了该方法,例如String类的equals()方法是比较对象的内容,也就是字符串的,而不是地址

JDK API文档对equals()说明如下:

equals 方法在非空对象引用上实现相等关系:

  • 自反性:对于任何非空引用值 xx.equals(x) 都应返回 true
  • 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true
  • 传递性:对于任何非空引用值 xy 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true
  • 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 xx.equals(null) 都应返回 false

同时这些性质也是我们在重写equals()需要满足的要求

3.getClass()

我们先来看一下JDK API文档对getclass的说明:

返回此 Object 的运行时类。返回的 Class 对象是由所表示类的 static synchronized 方法锁定的对象。

再看一下源码:

public final Class<?> getClass() {
      return shadow$_klass_; }

注意到final关键字,说明这个方法是不能被Object的子类重写的,我们在类中调用的getclass()方法都是调用的Object下面的,且文档对此的说明是返回运行时的对象,举个栗子:

[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析
 1 import java.util.Date;
2 public  class Test extends Date
3 {
4       public void test()
5       {
6               System.out.println(super.getClass().getName());
7       }
8       public static void main(String[] args) 
9       {
10               new Test().test();
11       }
12 }
[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析

输出:Test

有没有觉得很奇怪?输出的竟然是Test,其实一点也不奇怪,我们调用的getClass()是Object的,且它不能被重写,返回的是类运行时的对象,在代码中运行时的类是Test,虽然Test继承与Date,但是如果我们想返回的是Date类,那么我们运行时的对象应该是Date:Date date =  new Date();,现在运行时的类就是Date,返回的就是Date。

那么现在又有一个问题,getClass()和getName(),好像他们的输出值都是一样的,那是不是他们就没有区别呢?不是的,话不多说,直接撸源码:

public String getName() {

    if (name == null)
        name = getName0();
    return name;
    }
public final native Class<?> getClass();

getClass()返回的是一个对象,而getName返回的是一个字符串,虽然他们输出都是一样,但是我们必须知道控制台输出任何对象,非基础类型都会转化为字符串输出,所以二者的输出虽然看上去是一样的,但是有很大的区别

4.clone()

JDK API文档对说明:

创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。这样做的目的是,对于任何对象 x,表达式:

x.clone() != x

为 true,表达式:

x.clone().getClass() == x.getClass()

也为 true,但这些并非必须要满足的要求。一般情况下:

x.clone().equals(x)

为 true,但这并非必须要满足的要求。

我们再来看看源码:

protected native Object clone() throws CloneNotSupportedException;

我的理解是clone()就是将这个对象重新复制一遍,变成两个对象,都有着自己的内存空间,而不是只建立一个引用,指向同一片内存空间,注意到x.clone() != x,说明二者是两个对象,二者的内存空间都是不同的,所以将二者的地址进行比较的时候,肯定是不相等的,注意到x.clone().getClass() == x.getClass(),说明二者运行时的类都是相同的,注意到x.clone().equals(x),虽然不要求一定使这个等式为True,但是一般情况下都是成立的,比如String包装类,就是说明这二者的字符串是一样的,但是如果是自定义类等其他类且equals()没有被重写的话,那么这个等式是不成立的,因为equals()没有被重写,那么使用的就是Object下的equals(),比较的是对象的地址,所以等式不成立

那么为什么要有clone()呢?

我们现在先看一下clone()与=的区别,举个栗子:

有一个Car类
Car c1 = new Car();
Car c2 = c1;
这两句事实上只创建了一个对象。只不过c1和c2指向了同一个对象。
如果上面的两句改为:
Car c1 = new Car();
Car c2 = c1.clone();
那么就有了两个对象,而且这两个对象的内容是一样的。(所有的属性值相同)

假如现在我们有一个类,它有100个属性,那么我们如果这样:a.name=b.name;这样的话我们需要进行100此操作,这样也太不艺术了。。。。,所以我们就有了clone()方法,所有操作一次性完成!

那么我们现在来看一下clone的具体操作:

要知道我们的java语言取消了指针这个东东,导致了很多人都忽略了对象和引用的区别,java不能通过简单的赋值来解决对象的复制问题

看看下面这个栗子也可以解释我们为什么要有clone()

[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析
 1  A a1=new A();    
2  A a2=new A();    
3  a1.name="a1";    
4  a2=a1;    
5  a2.name="a2";    
6  System.out.println("a1.name="+a1.name);    
7  System.out.println("a2.name="+a2.name);    
8 //此时输出的结果是:  
9  //a1.name=a2    
10  //a2.name=a2    
[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析

如果我们要用a2保存a1对象的数据,但又不希望a2对象数据被改变时不影响到a1。实现clone()方法是其一种最简单,也是最高效的手段。下面我们来看看clone的分类

浅层clone():用于类属性只有Java基本类型和String类型的时候

深层clone:当类属性有数组和复杂类型的时候

彻底clone:当类属性有更加复杂的类型的时候,比如Vector对象容器

下面我们用栗子来进行说明:

浅层复制

[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析
 1 package test;
2
3 public class A implements Cloneable{
4 public String name;
5 public Object clone() {
6 A obj = null;
7 try {
8 obj = (A)super.clone();
9 } catch (CloneNotSupportedException e) {
10 // TODO Auto-generated catch block
11 e.printStackTrace();
12 }
13 return obj;
14 }
15
16 public static void main(String[] args) {
17 // TODO Auto-generated method stub
18 A a1 = new A();
19 A a2 = new A();
20 a1.name="a1";
21 a2 = (A) a1.clone();
22 a2.name = "a2";
23 System.out.println("a1.name="+a1.name);
24 System.out.println("a2.name="+a2.name);
25 }
26 /*
27 * 结果:
28 * a1.name=a1
29 * a2.name=a2
30 */
31 }
[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析

这就是所谓的浅层复制,我们可以看到我们的name是字符串型的,如果我们的name是字符串数组类型的,仍然用浅层复制,那么又会有什么问题呢?看看下面的栗子:

[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析
 1 package test;
2
3 public class A implements Cloneable{
4 public String name[];
5 public A() {
6 name = new String[2];
7 }
8 public Object clone() {
9 A obj = null;
10 try {
11 obj = (A)super.clone();
12 } catch (CloneNotSupportedException e) {
13 // TODO Auto-generated catch block
14 e.printStackTrace();
15 }
16 return obj;
17 }
18
19 public static void main(String[] args) {
20 // TODO Auto-generated method stub
21 A a1 = new A();
22 A a2 = new A();
23 a1.name[0]="a";
24 a1.name[1]="1";
25 a2=(A)a1.clone();
26 a2.name[0]="b";
27 a2.name[1]="1";
28 System.out.println("a1.name="+a1.name);
29 System.out.println("a1.name="+a1.name[0]+a1.name[1]);
30 System.out.println("a2.name="+a2.name);
31 System.out.println("a2.name="+a2.name[0]+a2.name[1]);
32 /*
33 * 结果:
34 * a1.name=[Ljava.lang.String;@7852e922
35 * a1.name=a1
36 * a2.name=[Ljava.lang.String;@4e25154f
37 * a2.name=a2
38 */
39 /*分析:
40 * 可以看到我们的a1.name输出的是name数组的地址
41 * 且输出的a1.name和a2.name都是b1,说明在进行对象复制的时候对name数组只是复制了它的地址,如果要解决这种情况的话,我们就要采用深层复制
42 */
43 }
44 }
[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析

深层clone()

[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析
 1 package test;
2
3 public class A implements Cloneable{
4 public String name[];
5 public A() {
6 name = new String[2];
7 }
8 public Object clone() {
9 A obj = null;
10 try {
11 obj = (A) super.clone();
12 obj.name=(String[])name.clone();//这是实现方式
13 } catch (CloneNotSupportedException e) {
14 // TODO Auto-generated catch block
15 e.printStackTrace();
16 }
17 return obj;
18 }
19
20 public static void main(String[] args) {
21 // TODO Auto-generated method stub
22 A a1 = new A();
23 A a2 = new A();
24 a1.name[0]="a";
25 a1.name[1]="1";
26 a2=(A)a1.clone();
27 a2.name[0]="b";
28 a2.name[1]="1";
29 System.out.println("a1.name="+a1.name);
30 System.out.println("a1.name="+a1.name[0]+a1.name[1]);
31 System.out.println("a2.name="+a2.name);
32 System.out.println("a2.name="+a2.name[0]+a2.name[1]);
33 /*
34 * 结果:
35 * a1.name=[Ljava.lang.String;@7852e922
36 * a1.name=a1
37 * a2.name=[Ljava.lang.String;@4e25154f
38 * a2.name=b1
39 */
40 /*分析:
41 * 可以看到我们输出的a1.name=a1b1.name=b1,说明我们在复制对象的时候数组name的负责传递的不是地址,而是开辟了另外的一片储存空间,而不是两个引用指向同一片内存空间
42 *
43 */
44 }
45 }
[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析

这就是深层复制,可是如果我们类的属性有Vector等储存对象地址的容器的时候,我们就必须进行彻底的clone(),栗子如下:

彻底clone()

[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析
 1 package test;
2
3 public class B implements Cloneable{
4 public String x="520";
5 public Object clone() {
6 B obj = null;
7 try {
8 obj = (B) super.clone();
9 } catch (CloneNotSupportedException e) {
10 // TODO Auto-generated catch block
11 e.printStackTrace();
12 }
13 return obj;
14 }
15 }
16
17 package test;
18
19 import java.util.Vector;
20 public class A implements Cloneable{
21 public String name[];
22 public Vector<B> clab;
23 public A() {
24 name = new String[2];
25 clab=new Vector<B>();
26 }
27 public Object clone() {
28 A obj = null;
29 try {
30 obj = (A) super.clone();
31 obj.name=(String[])name.clone();//这是实现方式
32 obj.clab=new Vector<B>();
33 for(int i=0;i<clab.size();i++) {//彻底clone
34 B temp = (B)clab.get(i).clone();//class B 也必须实现clone方法
35 obj.clab.add(temp);
36 }
37 } catch (CloneNotSupportedException e) {
38 // TODO Auto-generated catch block
39 e.printStackTrace();
40 }
41 return obj;
42 }
43
44 public static void main(String[] args) {
45 // TODO Auto-generated method stub
46 A a1 = new A();
47 A a2 = new A();
48 B b1 = new B();
49 a1.name[0]="a";
50 a1.name[1]="1";
51 a1.clab.add(b1);
52
53 a2=(A)a1.clone();
54
55 System.out.println("a2.name="+a2.name);
56 System.out.println("a2.name="+a2.name[0]+a2.name[1]);
57 System.out.println(a2.clab.get(0).x);
58
59 /*
60 * 结果:
61 * a1.name=[Ljava.lang.String;@7852e922
62 * a1.name=a1
63 * 520
64 */
65 /*分析:
66 * 如果类的属性里面有对象容器,那么必须采用彻底负责
67 */
68 }
69 }
[ 转载 ]  Java基础10--关于Object类下所有方法的简单解析

5.toString()

先看看文档对此的说明:

返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。

总结一下就是返回这个对象的字符串表现形式

下面我们看看源码:

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

可以看到返回的字符串是由对象的名称和对象的hashcode组成的

6.finalize()

我们先来看一下文档对此的说明:

当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写 finalize 方法,以配置系统资源或执行其他清除。

finalize 的常规协定是:当 JavaTM 虚拟机已确定尚未终止的任何线程无法再通过任何方法访问此对象时,将调用此方法,除非由于准备终止的其他某个对象或类的终结操作执行了某个操作。finalize 方法可以采取任何操作,其中包括再次使此对象对其他线程可用;不过,finalize 的主要目的是在不可撤消地丢弃对象之前执行清除操作。例如,表示输入/输出连接的对象的 finalize 方法可执行显式 I/O 事务,以便在永久丢弃对象之前中断连接。

Object 类的 finalize 方法执行非特殊性操作;它仅执行一些常规返回。Object 的子类可以重写此定义。

protected void finalize() throws Throwable { }
}

总结一下就是:当某个对象被Java虚拟机回收的时候执行这个方法,如果我们想在这个对象被回收的时候做点什么,比如再次使此对象对其他线程可用,那么就需要我们重写这个方法。

7.notify(),notifyAll(),wait()

这3个方法就是专为线程而生的,要实现线程的安全我们有synchronized,而这3个方法就为线程的同步而准备的

首先我们先来了解一下线程同步和线程互斥

线程同步:就是协调步调,一个任务,多个线程完成,线程的执行有先后顺序,这个就叫线程同步

线程互斥:A线程调用a资源的时候B线程就不能调用a资源,得等到A线程调用该资源结束之后,B线程才能调用资源a,这个就叫线程互斥

wait():如果对象调用了该方法,就会使持有该对象的进程把对象的控制器交出去,然后处于等待状态

notify():如果对象调用了该方法,就会随机通知某个正在等待该对象控制器的线程可以继续运行

notifyAll():如果对象调用了该方法,就会通知所有正在等待该对象控制器的线程可以继续运行

同时wait还可以指定等待的时间长度,如果是默认的话,就一直等待直到被通知