java.lang.object

Object类是Java中其他所有类的祖先,没有Object类Java面向对象无从谈起。作为其他所有类的基类,Object具有哪些属性和行为,是Java语言设计背后的思维体现。

Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入。Object类没有定义属性,一共有13个方法,具体的类定义结构如下图:

1.类构造器public Object();

大部分情况下,Java中通过形如 new A(args..)形式创建一个属于该类型的对象。其中A即是类名,A(args..)即此类定义中相对应的构造函数。通过此种形式创建的对象都是通过类中的构造函数完成。为体现此特性,Java中规定:在类定义过程中,对于未定义构造函数的类,默认会有一个无参数的构造函数,作为所有类的基类,Object类自然要反映出此特性,在源码中,未给出Object类构造函数定义,但实际上,此构造函数是存在的。

当然,并不是所有的类都是通过此种方式去构建,也自然的,并不是所有的类构造函数都是public。

2.private static native void registerNatives();

registerNatives函数前面有native关键字修饰,Java中,用native关键字修饰的函数表明该方法的实现并不是在Java中去完成,而是由C/C++去完成,并被编译成了.dll,由Java去调用。方法的具体实现体在dll文件中,对于不同平台,其具体实现应该有所不同。用native修饰,即表示操作系统,需要提供此方法,Java本身需要使用。具体到registerNatives()方法本身,其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。

既然如此,可能有人会问,registerNatives()修饰符为private,且并没有执行,作用何以达到?其实,在Java源码中,此方法的声明后有紧接着一段静态代码块:

1privatestaticnativevoidregisterNatives();2static{3registerNatives();4}

3.protected native Object clone() throws CloneNotSupportedException;

看,clone()方法又是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法,具体的实现是有C/C++完成的。clone英文翻译为"克隆",其目的是创建并返回此对象的一个副本。形象点理解,这有一辆科鲁兹,你看着不错,想要个一模一样的。你调用此方法即可像变魔术一样变出一辆一模一样的科鲁兹出来。配置一样,长相一样。但从此刻起,原来的那辆科鲁兹如果进行了新的装饰,与你克隆出来的这辆科鲁兹没有任何关系了。你克隆出来的对象变不变完全在于你对克隆出来的科鲁兹有没有进行过什么操作了。Java术语表述为:clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。

明白了clone的含义后,接下来看看如果调用clone()函数对象进行此克隆操作。

首先看一下下面的这个例子:

1packagecom.corn.objectsummary;23importcom.corn.Person;45publicclassObjectTest {67publicstaticvoidmain(String[] args) {89Object o1 =newObject();10//The method clone() from the type Object is not visible11Object clone =o1.clone();12}1314}

例子很简单,在main()方法中,new一个Oject对象后,想直接调用此对象的clone方法克隆一个对象,但是出现错误提示:"The method clone() from the type Object is not visible"

why? 根据提示,第一反应是ObjectTest类中定义的Oject对象无法访问其clone()方法。回到Object类中clone()方法的定义,可以看到其被声明为protected,估计问题就在这上面了,protected修饰的属性或方法表示:在同一个包内或者不同包的子类可以访问。显然,Object类与ObjectTest类在不同的包中,但是ObjectTest继承自Object,是Object类的子类,于是,现在却出现子类中通过Object引用不能访问protected方法,原因在于对"不同包中的子类可以访问"没有正确理解。

"不同包中的子类可以访问",是指当两个类不在同一个包中的时候,继承自父类的子类内部且主调(调用者)为子类的引用时才能访问父类用protected修饰的成员(属性/方法)。 在子类内部,主调为父类的引用时并不能访问此protected修饰的成员。!(super关键字除外)

于是,上例改成如下形式,我们发现,可以正常编译:

1packagecom.corn.objectsummary;234publicclassObjectTest {56publicstaticvoidmain(String[] args) {7ObjectTest ot1 =newObjectTest();89try{10ObjectTest ot2 =(ObjectTest) ot1.clone();11}catch(CloneNotSupportedException e) {12//TODO Auto-generated catch block13e.printStackTrace();14}15}1617}

是的,因为此时的主调已经是子类的引用了。

上述代码在运行过程中会抛出"java.lang.CloneNotSupportedException",表明clone()方法并未正确执行完毕,问题的原因在与Java中的语法规定:

clone()的正确调用是需要实现Cloneable接口,如果没有实现Cloneable接口,并且子类直接调用Object类的clone()方法,则会抛出CloneNotSupportedException异常。

Cloneable接口仅是一个表示接口,接口本身不包含任何方法,用来指示Object.clone()可以合法的被子类引用所调用。

于是,上述代码改成如下形式,即可正确指定clone()方法以实现克隆。

1packagecom.corn.objectsummary;23publicclassObjectTestimplementsCloneable {45publicstaticvoidmain(String[] args) {67ObjectTest ot1 =newObjectTest();89try{10ObjectTest ot2 =(ObjectTest) ot1.clone();11System.out.println("ot2:" +ot2);12}catch(CloneNotSupportedException e) {13//TODO Auto-generated catch block14e.printStackTrace();15}16}1718}

更多的关于Java克隆/复制相关的知识以后会给出专门的博文。

4.public final native Class getClass();

getClass()也是一个native方法,返回的是此Object对象的类对象/运行时类对象Class。效果与Object.class相同。

首先解释下"类对象"的概念:在Java中,类是是对具有一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例。作为概念层次的类,其本身也具有某些共同的特性,如都具有类名称、由类加载器去加载,都具有包,具有父类,属性和方法等。于是,Java中有专门定义了一个类,Class,去描述其他类所具有的这些特性,因此,从此角度去看,类本身也都是属于Class类的对象。为与经常意义上的对象相区分,在此称之为"类对象"。

此处主要大量涉及到Java中的反射知识,关于反射相关知识后续也会给出相关博文。

5.public boolean equals(Object obj);

==与equals在Java中经常被使用,大家也都知道==与equals的区别:

==表示的是变量值完成相同(对于基础类型,地址中存储的是值,引用类型则存储指向实际对象的地址);

equals表示的是对象的内容完全相同,此处的内容多指对象的特征/属性。

实际上,上面说法是不严谨的,更多的只是常见于String类中。首先看一下Object类中关于equals()方法的定义:

1publicbooleanequals(Object obj) {2return(this==obj);3}

由此可见,Object原生的equals()方法内部调用的正是==,与==具有相同的含义。既然如此,为什么还要定义此equals()方法?

equlas()方法的正确理解应该是:判断两个对象是否相等。那么判断对象相等的标尺又是什么?

如上,在object类中,此标尺即为==。当然,这个标尺不是固定的,其他类中可以按照实际的需要对此标尺含义进行重定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。如此可以增加类的功能型和实际编码的灵活性。当然了,如果自定义的类没有重写equals()方法来重新定义此标尺,那么默认的将是其父类的equals(),直到object基类

如下场景的实际业务需求,对于User bean,由实际的业务需求可知当属性uid相同时,表示的是同一个User,即两个User对象相等。则可以重写equals以重定义User对象相等的标尺。

1packagecom.corn.objectsummary;23publicclassUser {45privateintuid;6privateString name;7privateintage;89publicintgetUid() {10returnuid;11}1213publicvoidsetUid(intuid) {14this.uid =uid;15}1617protectedString getName() {18returnname;19}2021publicvoidsetName(String name) {22this.name =name;23}2425publicintgetAge() {26returnage;27}2829publicvoidsetAge(intage) {30this.age =age;31}3233@Override34publicbooleanequals(Object obj) {35if(obj ==null|| !(objinstanceofUser)) {36returnfalse;37}38if(((User) obj).getUid() ==this.getUid()) {39returntrue;40}41returnfalse;42}43}

1packagecom.corn.objectsummary;23publicclassObjectTestimplementsCloneable {45publicstaticvoidmain(String[] args) {6User u1 =newUser();7u1.setUid(111);8u1.setName("张三");910User u2 =newUser();11u2.setUid(111);12u2.setName("张三丰");1314System.out.println(u1.equals(u2)); //返回true15}1617}

ObjectTest中打印出true,因为User类定义中重写了equals()方法,这很好理解,很可能张三是一个人小名,张三丰才是其大名,判断这两个人是不是同一个人,这时只用判断uid是否相同即可。

如上重写equals方法表面上看上去是可以了,实则不然。因为它破坏了Java中的约定:重写equals()方法必须重写hasCode()方法。

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()返回的哈希码可能相同。

因此,在上述代码中,重写了equals()方法后,需要重写hashCode()方法。

1packagecom.corn.objectsummary;23publicclassUser {45privateintuid;6privateString name;7privateintage;89publicintgetUid() {10returnuid;11}1213publicvoidsetUid(intuid) {14this.uid =uid;15}1617protectedString getName() {18returnname;19}2021publicvoidsetName(String name) {22this.name =name;23}2425publicintgetAge() {26returnage;27}2829publicvoidsetAge(intage) {30this.age =age;31}3233@Override34publicbooleanequals(Object obj) {35if(obj ==null|| !(objinstanceofUser)) {36returnfalse;37}38if(((User) obj).getUid() ==this.getUid()) {39returntrue;40}41returnfalse;42}4344@Override45publicinthashCode() {46intresult = 17;47result = 31 * result +this.getUid();48returnresult;49}50}

注:上述hashCode()的重写中出现了result*31,是因为result*31 = (result<<5) - result。之所以选择31,是因为左移运算和减运算计算效率远大于乘法运算。当然,也可以选择其他数字。

7.public String toString();

toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体:

1publicString toString() {2returngetClass().getName() + "@" +Integer.toHexString(hashCode());3}

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():唤醒在此对象监视器上等待的单个线程/所有线程。

wait(...) / notify() | notifyAll()一般情况下都是配套使用。下面来看一个简单的例子:

1packagecom.qqyumidi;23publicclassThreadTest {45/**6*@paramargs7*/8publicstaticvoidmain(String[] args) {9//TODO Auto-generated method stub10MyRunnable r =newMyRunnable();11Thread t =newThread(r);12t.start();13synchronized(r) {14try{15System.out.println("main thread 等待t线程执行完");16r.wait();17System.out.println("被notity唤醒,得以继续执行");18}catch(InterruptedException e) {19//TODO Auto-generated catch block20e.printStackTrace();21System.out.println("main thread 本想等待,但被意外打断了");22}23System.out.println("线程t执行相加结果" +r.getTotal());24}25}26}2728classMyRunnableimplementsRunnable {29privateinttotal;3031@Override32publicvoidrun() {33//TODO Auto-generated method stub34synchronized(this) {35System.out.println("Thread name is:" +Thread.currentThread().getName());36for(inti = 0; i < 10; i++) {37total +=i;38}39notify();40System.out.println("执行notif后同步代码块中依然可以继续执行直至完毕");41}42System.out.println("执行notif后且同步代码块外的代码执行时机取决于线程调度");43}4445publicintgetTotal() {46returntotal;47}48}

运行结果为:

1main thread 等待t线程执行完2Thread name is:Thread-03执行notif后同步代码块中依然可以继续执行直至完毕4执行notif后且同步代码块外的代码执行时机取决于线程调度  //此行输出位置有具体的JVM线程调度决定,有可能最后执行5被notity唤醒,得以继续执行6线程t执行相加结果45

既然是作用于多线程中,为什么却是Object这个基类所具有的方法?原因在于理论上任何对象都可以视为线程同步中的监听器,且wait(...)/notify()|notifyAll()方法只能在同步代码块中才能使用。

从上述例子的输出结果中可以得出如下结论:

1、wait(...)方法调用后当前线程将立即阻塞,且适当其所持有的同步代码块中的锁,直到被唤醒或超时或打断后且重新获取到锁后才能继续执行;

2、notify()/notifyAll()方法调用后,其所在线程不会立即释放所持有的锁,直到其所在同步代码块中的代码执行完毕,此时释放锁,因此,如果其同步代码块后还有代码,其执行则依赖于JVM的线程调度。

在Java源码中,可以看到wait()具体定义如下:

1publicfinalvoidwait()throwsInterruptedException {2wait(0);3}

且wait(long timeout, int nanos)方法定义内部实质上也是通过调用wait(long timeout)完成。而wait(long timeout)是一个native方法。因此,wait(...)方法本质上都是native方式实现。

notify()/notifyAll()方法也都是native方法。

Java中线程具有较多的知识点,是一块比较大且重要的知识点。后期会有博文专门针对Java多线程作出详细总结。此处不再细述。

13. protected void finalize();

finalize方法主要与Java垃圾回收机制有关。首先我们看一下finalized方法在Object中的具体定义:

1protectedvoidfinalize()throwsThrowable { }

我们发现Object类中finalize方法被定义成一个空方法,为什么要如此定义呢?finalize方法的调用时机是怎么样的呢?

首先,Object中定义finalize方法表明Java中每一个对象都将具有finalize这种行为,其具体调用时机在:JVM准备对此对象所占用的内存空间进行垃圾回收前,将被调用。由此可以看出,此方法并不是由我们主动去调用的(虽然可以主动去调用,此时与其他自定义方法无异)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,602评论 18 399
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,087评论 0 62
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,218评论 11 349
  • 自以为是的自以为是 就像猫摘下了帽子,我剃光了胡子。 听说明天不会更好,所以今天不会更糟 来了就请入座 狐朋狗友萍...
    漻涸阅读 242评论 1 3
  • 1.如何对 Android 应用进行性能分析 2.什么情况下会导致内存泄露 3.如何避免 OOM 异常 4.And...
    Hsveico阅读 2,323评论 0 31