[ 转载 ] Java基础10--关于Object类下所有方法的简单解析
关于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
方法在非空对象引用上实现相等关系:
- 自反性:对于任何非空引用值
x
,x.equals(x)
都应返回true
。 - 对称性:对于任何非空引用值
x
和y
,当且仅当y.equals(x)
返回true
时,x.equals(y)
才应返回true
。 - 传递性:对于任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,并且y.equals(z)
返回true
,那么x.equals(z)
应返回true
。 - 一致性:对于任何非空引用值
x
和y
,多次调用 x.equals(y) 始终返回true
或始终返回false
,前提是对象上equals
比较中所用的信息没有被修改。 - 对于任何非空引用值
x
,x.equals(null)
都应返回false
同时这些性质也是我们在重写equals()需要满足的要求
3.getClass()
我们先来看一下JDK API文档对getclass的说明:
返回此 Object
的运行时类。返回的 Class
对象是由所表示类的 static synchronized
方法锁定的对象。
再看一下源码:
public final Class<?> getClass() {
return shadow$_klass_; }
注意到final关键字,说明这个方法是不能被Object的子类重写的,我们在类中调用的getclass()方法都是调用的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 }

输出: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()

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

如果我们要用a2保存a1对象的数据,但又不希望a2对象数据被改变时不影响到a1。实现clone()方法是其一种最简单,也是最高效的手段。下面我们来看看clone的分类
浅层clone():用于类属性只有Java基本类型和String类型的时候
深层clone:当类属性有数组和复杂类型的时候
彻底clone:当类属性有更加复杂的类型的时候,比如Vector对象容器
下面我们用栗子来进行说明:
浅层复制

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 }

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

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 }

深层clone()

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 }

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

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 }

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还可以指定等待的时间长度,如果是默认的话,就一直等待直到被通知
[ 转载 ] Java基础10--关于Object类下所有方法的简单解析的更多相关文章
- 关于Object类下所有方法的简单解析
类Object是类层次结构的根类,是每一个类的父类,所有的对象包括数组,String,Integer等包装类,所以了解Object是很有必要的,话不多说,我们直接来看jdk的源码,开始我们的分析之路 ...
- Java基础教程(19)--Object类
Object类位于类结构树的最顶端,所有的类都是它的直接或间接子类,因此所有的类都继承了Object类的方法,我们可以在需要的时候覆盖这些方法.下面是一些将会在本文中讨论的Object类的方法: ...
- java基础学习总结——Object类
一.Object类介绍
- java基础篇之Object类
1.Object类是所有类的超类 2.Object类的equals方法 public boolean equals(Object obj) {return (this == obj);} equals ...
- Java 基础 常用API (Object类,String类,StringBuffer类)
Java API Java 的API(API: Application(应用) Programming(程序) Interface(接口)) Java API就是JDK中提供给我们使用的类,这些类将底 ...
- [ 转载 ] Java基础14--创建线程的两个方法
http://www.cnblogs.com/whgw/archive/2011/10/03/2198506.html Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类 ...
- JAVA基础系列:Object类
1. 万物皆对象 1. JVM在编译源代码时,在遇到没有继承Object的对象的时候,编译器会默认指定一个默认的父类Object 2. Object 和接口的关系,接口是否继承Object?接口没有继 ...
- 【java基础 10】hash算法冲突解决方法
导读:今天看了java里面关于hashmap的相关源码(看了java6和java7),尤其是resize.transfer.put.get这几个方法,突然明白了,为什么我之前考数据结构死活考不过,就差 ...
- 转载-java基础学习汇总
共2页: 1 2 下一页 Java制作证书的工具keytool用法总结 孤傲苍狼 2014-06-24 11:03 阅读:25751 评论:3 Java基础学习总结——Java对象的序列化和 ...
随机推荐
- HDU 1719 Friend 规律题
解题报告:规定数字1和数字2是Friend number,然后规定,若a和b是Friend number,那么a*b+a+b也是Friend number,输入一个数,让你判断这个数是否是Friend ...
- Python概念-定制自己的数据类型(包装)
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均 ...
- Sysmon + NXlog构建简单的windows安全监控
工具: Sysmon (sysmon 5.0) ,NXlog(nxlog-ce-2.9.1716.msi) . Sysmon监控系统并生成windows event log, NXlog将wind ...
- Strusts2笔记4--类型转换器
类型转换器: Struts2默认情况下可以将表单中输入的文本数据转换为相应的基本数据类型.这个功能的实现,主要是由于Struts2内置了类型转换器.这些转换器在struts-default.xml中可 ...
- ubuntu复制文件或目录
转自http://www.linuxidc.com/Linux/2008-11/17179.htm cp(copy)命令 该命令的功能是将给出的文件或目录拷贝到另一文件或目录中. 语法: cp [选项 ...
- Linux时间子系统之七:定时器的应用--msleep(),hrtimer_nanosleep()【转】
转自:http://blog.csdn.net/droidphone/article/details/8104433 我们已经在前面几章介绍了低分辨率定时器和高精度定时器的实现原理,内核为了方便其它子 ...
- Oracle 11G R2 RAC中的scan ip 的用途和基本原理【转】
Oracle 11G R2 RAC增加了scan ip功能,在11.2之前,client链接数据库的时候要用vip,假如你的cluster有4个节点,那么客户端的tnsnames.ora中就对应有四个 ...
- MySQL 四种链接
1.内联接 INNER JOIN(典型的联接运算,使用像 = 或 <> 之类的比较运算符).包括相等联接和自然联接. 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行. ...
- 【前端开发】localStorage的用法
localStorage.setItem("name","value") //存储name的值 var type = localStorage.getItem ...
- python网络编程--进程池
一:进程池 进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程, 如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止. 进程池中有两个方法: apply a ...