结合Java参考文档,Object类的描述如下:
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.
解释就是说Object类时所有层类的根,每一个类都将Object 类作为超类。所有的对象,包括数组,都实现了这个类的方法。简而言之,所有的类都是从Object类拓展过来的,无论是JDK提供的类,还是程序编写人员自己编写的类,都有一个默认的父类:Object
Object类图如下:
1.其中第一个就是空参的构造器Object();
public Object():所有的java类在默认情况下都会提供一个空参构造器,当然Object()也不例外,只是没有具体实现的内容
2.private void native registerNatives()
这个方法被声明为native 说明具体实现使用C/C++实现的,通过java jni来完成方法的引入,具体怎么实现在我的博文中,简要过程就是原C/C++去完成实现并被编译成了.dll,由Java去调用。方法的具体实现体在dll文件中,对于不同平台,其具体实现应该有所不同。用native修饰,即表示操作系统,需要提供此方法,Java本身需要使用。具体到registerNatives()方法本身,其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。
既然如此,可能有人会问,registerNatives()修饰符为private,且并没有执行,作用何以达到?其实,在Java源码中,此方法的声明后有紧接着一段静态代码块:
private static native void registerNatives();
static {
registerNatives();
}
有两个方法是protected的colne()和finalize()分别表示克隆和释放内存空间
3.protected native Object clone() throws CloneNotSupportedException;
看,clode()方法又是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法,具体的实现是有C/C++完成的。clone英文翻译为"克隆",其目的是创建并返回此对象的一个副本。形象点理解,这有一辆科鲁兹,你看着不错,想要个一模一样的。你调用此方法即可像变魔术一样变出一辆一模一样的科鲁兹出来。配置一样,长相一样。但从此刻起,原来的那辆科鲁兹如果进行了新的装饰,与你克隆出来的这辆科鲁兹没有任何关系了。你克隆出来的对象变不变完全在于你对克隆出来的科鲁兹有没有进行过什么操作了。Java术语表述为:clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。
我们看一下具体实现的代码:package com.jdk;
public class TestObject {
public static void main(String[] args) {
TestObject o = new TestObject();
try {
TestObject o1 = (TestObject) o.clone();
System.out.println(o);
System.out.println(o1);
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这样编译的时候会通过,但是运行的时候就会发生CloneNotSupportedException这个异常,这是因为一个类只有实现了Clonable接口后才能调用克隆方法,因此代码如下:
package com.jdk;
public class TestObject implements Cloneable{
public static void main(String[] args) {
TestObject o = new TestObject();
try {
TestObject o1 = (TestObject) o.clone();
System.out.println(o);
System.out.println(o1);
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
其中o,o1占用两个不同的栈空间(这个不言自明),同时还指向不同的堆空间,如果是o1=o的话,那么他们指向的就是相同的堆空间了。
4.public final native Class<?> getClass();
getClass()也是一个native方法,返回的是此Object对象的类对象/运行时类对象Class<?>。效果与Object.class相同。
首先解释下"类对象"的概念:在Java中,类是是对具有一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例。作为概念层次的类,其本身也具有某些共同的特性,如都具有类名称、由类加载器去加载,都具有包,具有父类,属性和方法等。于是,Java中有专门定义了一个类,Class,去描述其他类所具有的这些特性,因此,从此角度去看,类本身也都是属于Class类的对象。为与经常意义上的对象相区分,在此称之为"类对象"。
5.public boolean equals(Object obj);
“==”与equals在Java中经常被使用,大家也都知道”==“与equals的区别:
”==“表示的是变量值完成相同(对于基础类型,地址中存储的是值,引用类型则存储指向实际对象的地址);
equals表示的是对象的内容完全相同,此处的内容多指对象的特征/属性。但是Object类中提供的实现为
<span style="font-size:14px;">public boolean equals(Object obj) {
return (this == obj);
}</span>
这个将“==”与equals混为一谈实则不是,通常我们在编写一个类时会重写equals方法,来规定两个对象相等的规则
通常在编写equals方法是这样写
public boolean equals(Object obj) {
//如果该对象指向的地址与比较对象相同返回的是true
if (this == obj)
return true;
//如果obj为null返回为false,因为如果调用该方法的对象为null,则会出现NullPointerException
if (obj == null)
return false;
//如果两个对象的类不同,返回一定为false
if (getClass() != obj.getClass())
return false;
//如果相同将obj强制转换为Person
Person other = (Person) obj;
//逐个比较各个属性是否相等,其中基本数据类型判断依据为“==”,装箱类型与String都实现了
//自己的equals方法,直接调用就可以了
if (age == null) {
if (other.age != null)
return false;
} else if (!age.equals(other.age))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
6.public native int hashCode();
hashCode()方法返回一个整形数值,表示该对象的哈希码值。
hashCode()具有如下约定:
1).在Java应用程序程序执行期间,对于同一对象多次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未做修改。在Java应用程序的一次执行到另外一次执行,同一对象的hashCode()返回的哈希码无须保持一致;
2).如果两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等;
3).反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不一定相等。
即严格的数学逻辑表示为: 两个对象相等 <=> equals()相等 => hashCode()相等。因此,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时可以推理出:hasCode()不相等 => equals()不相等 <=> 两个对象不相等。
可能有人在此产生疑问:既然比较两个对象是否相等的唯一条件(也是冲要条件)是equals,那么为什么还要弄出一个hashCode(),并且进行如此约定,弄得这么麻烦?
其实,这主要体现在hashCode()方法的作用上,其主要用于增强哈希表的性能。
以集合类中,以Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现)
在此需要纠正一个理解上的误区:对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode()相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode()返回的哈希码可能相同。
因为是native方法,所以Object没有给出具体的实现,但是在API文档里有@linked java.util.HashMap说明具体实现的地方可以参考,也说明了其主要用途在集合类中生成hash值看Person类中重写hashcode()方法
public int hashCode() {
//选取31作为乘数有以下原因:
//31是质数,除了1另外一个就是本身,使用hashcode值来确定元素在容器中的位置,要尽量减少冲突
//来提高查询效率,所以选取一个足够长的的系数(31=1111[2])同时要保证相乘以后尽量保证不能溢
//出,此外i*31=(i<<5)-1,在虚拟机中会对此进行优化,以提高效率。
final int prime = 31;
int result = 1;
result = prime * result + ((age == null) ? 0 : age.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
7.public String toString();
toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString()方法相信大家都经常用到,即使没有显式调用,但当我们使用System.out.println(obj)时,其内部也是通过toString()来实现的。
getClass()返回对象的类对象,getClassName()以String形式返回类对象的名称(含包名)。Integer.toHexString(hashCode())则是以对象的哈希码为实参,以16进制无符号整数形式返回此哈希码的字符串表示形式。
如上例中的u1的哈希码是638,则对应的16进制为27e,调用toString()方法返回的结果为:com.corn.objectsummary.User@27e。
因此:toString()是由对象的类型和其哈希码唯一确定,同一类型但不相等的两个对象分别调用toString()方法返回的结果可能相同。
8/9/10/11/12. wait(...) / notify() / notifyAll()
一说到wait(...) / notify() | notifyAll()几个方法,首先想到的是线程。确实,这几个方法主要用于java多线程之间的协作。先具体看下这几个方法的主要含义:
wait():调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法。
wait(long timeout)/wait(long timeout, int nanos):调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notisfy()/notisfyAll()方法,或超过指定的超时时间量。
notify()/notifyAll():唤醒在此对象监视器上等待的单个线程/所有线程。
13. protected void finalize();
finalize方法主要与Java垃圾回收机制有关。首先我们看一下finalized方法在Object中的具体定义:
<span style="font-size:14px;">protected void finalize() throws Throwable { }</span>
我们发现Object类中finalize方法被定义成一个空方法,为什么要如此定义呢?finalize方法的调用时机是怎么样的呢?
首先,Object中定义finalize方法表明Java中每一个对象都将具有finalize这种行为,其具体调用时机在:JVM准备对此对形象所占用的内存空间进行垃圾回收前,将被调用。由此可以看出,此方法并不是由我们主动去调用的(虽然可以主动去调用,此时与其他自定义方法无异)。