深入Java基础(一)--Object类分析

Object类是java中所有类的父类,所有类默认(而非显式)继承Object。这也就意味着,Object类中的所有公有方法也将被任何类所继承。如果,整个java类体系是一颗树,那么Object类毫无疑问就是整棵树的根。

文章结构:

1)源码分析
2)浅拷贝与深拷贝
3)探讨hashcode与equals的设计使用。

一、源码分析

public   class  Object {   

    /* 一个本地方法,具体是用C(C++)在DLL中实现的,然后通过JNI调用。*/     
     private   static   native   void  registerNatives(); 

   /* 对象初始化时自动调用此方法*/  
     static  {   
        registerNatives();   
    }   

    /* 返回这个Object的运行时Class对象。这个Class对象被所代表的类的static synchronized 
      方法锁定。*/   
     public   final   native  Class<?> getClass();   

/*   
hashCode 的常规协定是:(本质 上是 返回该对象的哈希码值。 )

1.同一个对象在没修改的情况下的hashCode必须相同。在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。    
2.如果根据 equals(Object) 方法比较,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。    
3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。支持这个方法是为了提高哈希表的性能,例如HashMap。 
实际上,被Object类定义的hashCode方法对不同的对象确实返回不同的整数。(这是通过 
把对象的内部地址转化为一个整数来实现的,但是这个实现技巧不被Java编程语言需要。)
*/   

     public   native   int  hashCode();   

  //没有重写过的equals,就是使用==,比较内存地址。
     public   boolean  equals(Object obj) {   
        return  ( this  == obj);   
    }   

     /*本地CLONE方法,用于对象的复制。*/   
     protected   native  Object clone()  throws  CloneNotSupportedException;   

     /*返回该对象的字符串表示。非常重要的方法*/   
     public  String toString() {   
     return  getClass().getName() +  "@"  + Integer.toHexString(hashCode());   
    }   

    /*唤醒在此对象监视器上等待的单个线程。*/   
     public   final   native   void  notify();   

    /*唤醒在此对象监视器上等待的所有线程。*/   
     public   final   native   void  notifyAll();   


/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。    
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。*/   
     public   final   void  wait()  throws  InterruptedException {   
    wait( 0 );   
    }   



    /*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。*/   
     public   final   native   void  wait( long  timeout)  throws  InterruptedException;   

     /* 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。*/   
     public   final   void  wait( long  timeout,  int  nanos)  throws  InterruptedException {   
         if  (timeout <  0 ) {   
             throw   new  IllegalArgumentException( "timeout value is negative" );   
        }   

         if  (nanos <  0  || nanos >  999999 ) {   
             throw   new  IllegalArgumentException(   
                 "nanosecond timeout value out of range" );   
        }   

     if  (nanos >=  500000  || (nanos !=  0  && timeout ==  0 )) {   
        timeout++;   
    }   

    wait(timeout);   
    }   

     /*当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。*/   
     protected   void  finalize()  throws  Throwable { }   
} 

equals方法详解:

可以看出默认情况下equals进行对象比较时只判断了对象是否是其自身(也就是对比的是内存地址),当我们有特殊的“相等”逻辑时,则需要覆盖equals方法。

equals方法的通用约定:

  • 1)自反性:对于任何非null的引用值x,x.equals(x)必须返回true。
  • 2)对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
  • 3)传递性:对于任何非null的引用值x、y、z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也必须返回ture。
  • 4)一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的 信息 没有被修改,多次调用x.equals(y)就会一致的返回ture,或者一致的返回false。
  • 5)非空性:对于任何非null的引用值x,x.equals(null)必须返回false。

clone方法详解:

1)简介:

clone方法将创建和返回该对象的一个拷贝。这个“拷贝”的精确含义取决于该对象的类。一般的含义是,对于任何对象x,表达式x.clone() != x 将会是true,并且,表达式x.clone().getClass() == x.getClass()将会是true。并且通常情况下,表达式x.clone().equals(x)将会是true。
clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。但是hashCode会不一样了。

2)一个标准的clone实现需要做到的两点:

1)调用super.clone()方法
2)对于对象中的所有引用类型,均需要实现Cloneable接口,并重写clone方法,然后对每个引用执行clone方法。

@Override
protected Fruit clone() throws CloneNotSupportException{
    Fruit fruit = (Fruit) super.clone();
    fruit.color = new String(color);
    return fruit;
}

3)使用clone方法的优点:

1)速度快。clone方法最终会调用Object.clone()方法,这是一个native方法,本质是内存块复制,所以在速度上比使用new创建对象要快。
2)灵活。可以在运行时动态的获取对象的类型以及状态,从而创建一个对象。

4)使用clone方法创建对象的缺点同样非常明显:

1)实现深拷贝较为困难,需要整个类继承系列的所有类都很好的实现clone方法。
2)需要处理CloneNotSupportedException异常。Object类中的clone方法被声明为可能会抛出CloneNotSupportedException,因此在子类中,需要对这一异常进行处理。

建议:对于浅拷贝,我们不应该实现Cloneable接口,而应该使用拷贝构造器或者拷贝工厂。

为什么?
它们不依赖于对象创建机制;它们不会与final域的正常使用发生冲突;它们不会抛出不必要的受检异常(check exception);他们不需要进行类型转换。
更关键的,这样更加的面向对象、


二、浅拷贝与深拷贝:

任何变成语言中,其实都有浅拷贝和深拷贝的概念,Java 中也不例外。我们需要对其概念非常清晰,才能帮助自己快速排查问题。

(1)什么是浅拷贝和深拷贝:

首先我们知道拷贝是针对一个已有对象的操作。
在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 = 号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。

而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。

(2)举例说明:

浅拷贝:

public class CloneDemo implements Cloneable {
    public String name;
    public CloneChildDemo cloneChildDemo;
    @Override
    public Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class CloneChildDemo{
    private String name;
}

场景验证:

    public static void main(String[]args){
        CloneDemo cloneDemo = new CloneDemo();
        cloneDemo.name = "辅助";
        cloneDemo.cloneChildDemo = new CloneChildDemo();
        CloneDemo cloneDemo1 = (CloneDemo) cloneDemo.clone();

        System.out.println(cloneDemo.name);
        System.out.println(cloneDemo1.name);
        System.out.println(cloneDemo.hashCode());
        System.out.println(cloneDemo1.hashCode());
        System.out.println(cloneDemo == cloneDemo1);
        System.out.println("--------------");
        System.out.println(cloneDemo.cloneChildDemo == cloneDemo1.cloneChildDemo);
        System.out.println(cloneDemo.cloneChildDemo.hashCode());
        System.out.println(cloneDemo1.cloneChildDemo.hashCode());
    }
/*
辅助
辅助
460141958
1163157884
false
------------
true
159413332
159413332
*/

可以看到,使用 clone() 方法,从 == 和 hashCode 的不同可以看出,clone() 方法实则是真的创建了一个新的对象。
但是,这只是一个浅拷贝操作。为什么?从最后对 cloneChildDemo的输出可以看到,cloneDemo 和cloneDemo1 的 child 对象,实际上还是指向了统一个对象,只对它的引用进行了传递。

深拷贝:

既然已经了解了对 clone() 方法,只能对当前对象进行浅拷贝,引用类型依然是在传递引用。
那么我们如何进行一次深拷贝?
1)序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象,无非就是序列化的规则需要我们自己来写。
2)继续利用 clone() 方法,既然 clone() 方法,是我们来重写的,实际上我们可以对其内的引用类型的变量,再进行一次 clone()。

public class CloneDemo implements Cloneable {
    public String name;
    public CloneChildDemo cloneChildDemo;

    @Override
    public Object clone(){
        try {
            CloneDemo cloneDemo = (CloneDemo) super.clone();
            cloneDemo.cloneChildDemo = (CloneChildDemo) this.cloneChildDemo.clone();
            return cloneDemo;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
 }

验证深拷贝:

public static void main(String[]args){
        CloneDemo cloneDemo = new CloneDemo();
        cloneDemo.name = "辅助";
        cloneDemo.cloneChildDemo = new CloneChildDemo();
        CloneDemo cloneDemo1 = (CloneDemo) cloneDemo.clone();

        System.out.println(cloneDemo.name);
        System.out.println(cloneDemo1.name);
        System.out.println(cloneDemo.hashCode());
        System.out.println(cloneDemo1.hashCode());
        System.out.println(cloneDemo == cloneDemo1);
        System.out.println("--------------");
        System.out.println(cloneDemo.cloneChildDemo == cloneDemo1.cloneChildDemo);
        System.out.println(cloneDemo.cloneChildDemo.hashCode());
        System.out.println(cloneDemo1.cloneChildDemo.hashCode());
    }
/*
辅助
辅助
1349393271
1338668845
false
--------------
false
159413332
1028214719
*/

(3)使用建议:

针对浅拷贝,我们不应该实现Cloneable接口,而应该使用拷贝构造器或者拷贝工厂。
而对于深拷贝,还是推荐使用 clone() 方法,这样只需要每个类自己维护自己即可,而无需关心内部其他的对象中,其他的参数是否也需要 clone() 。

三、探讨hashcode与equals的设计使用:

我们先来看来两段代码:

(1)不重写hashcode与equals:

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
    public static void main(String[]args){
        HashMap hm = new HashMap();
        Person person = new Person ("辅助");
        Person person1 = new Person ("辅助");
        hm.put(person , "a");
        hm.put(person1 , "b");
        System.out.println(hm.size());
    }
}
//输出size为2

}

(2)重写hashcode与equals:

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public int hashCode(){
        return Objects.hash(name);
    }
    @Override
    public boolean equals(Object obj) {
        if(this == obj){
            return true;
        }
        if(obj instanceof Person){
            Person person=(Person)obj;
            if(this.name.equals(person.name)){
                return true;
            }
        }
        return false;
    }
        public static void main(String[]args){
        HashMap hm = new HashMap();
        Person person = new Person ("辅助");
        Person person1 = new Person ("辅助");
        hm.put(person , "a");
        hm.put(person1 , "b");
        System.out.println(hm.size());
    }
}
//输出是1

从上面可以看出,我们重写hashCode与equals方法时,注意自己的设计,取得不好只是会影响容器的效率以及影响到你插入重复key。

(3)为什么会这样?

首先,我们阅读源码知道HashMap等相关容器 的设计就是对象的hashCode进行比较,去重设计。
然后我们思考一下,我们已经可以通过equals方法进行比较了,甚至原始的Objuct可以比较内存地址,那我们为什么还需要一个hashCode的去进行重写?
试想一下,如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值。然后容器基于这个的hashCode值进行设计运作。这样的话,实际调用equals方法的次数就大大降低了。

所以就两点原因:

1)hashCode()方法存在的主要目的就是提高效率。
2)在集合中判断两个对象相等的条件,其实无论是往集合中存数据,还是从集合中取数据,包括如果控制唯一性等,都是用这个条件判断的。


结语

好了,深入Java基础(一)--Object类分析讲完了。是自己大四开始回归博客世界的第一篇,继续总结分享,欢迎各位前来交流。


更多文章点此处

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

推荐阅读更多精彩内容