Java 基础 (7) -- Object 类

1. 概念

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

  • Object 类位于 java.lang 包中,java.lang 包包含着 Java 最基础和核心的类,在编译时会自动导入。

  • Object 类没有定义属性,一共有 13 个方法,13 个方法之中并不是所有方法都是子类可以访问的,registerNatives()就不可以访问

2. 13 个方法

1. 默认构造器

public Object(){}:类构造器,源码中没有给出 Object 类的构造器,但默认存在

2. registerNatives()

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 源码中,此方法的声明后有紧接着一段静态代码块

    private static native void registerNatives();  
    static {  
         registerNatives();  
    } 
    

3. clone()

protected native Object clone() throwsCloneNotSupportedException:

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

  • clone() 方法是一个被声明为 native 的方法,因此,我们知道了 clone() 方法并不是 Java 的原生方法,具体的实现是由 C/C++ 完成的。clone 英文翻译为"克隆",其目的是创建并返回此对象的一个副本

  • clone() 的正确调用是需要实现 Cloneable 接口,如果没有实现 Cloneable 接口,并且子类直接调用Object 类的 clone() 方法,则会抛出 CloneNotSupportedException 异常。Cloneable 接口仅是一个表示接口,接口本身不包含任何方法,用来指示 Object.clone() 可以合法的被子类引用所调用

  • clone 方法实现的是浅拷贝,只拷贝当前对象,并且在堆中分配新的空间,放这个复制的对象。但是对象如果里面有其他类的子对象,那么就不会拷贝到新的对象中

    public class User implements Cloneable{
    public int id;
    public String name;
    public UserInfo userInfo;
    
    public static void main(String[] args) {
        User user = new User();
        UserInfo userInfo = new UserInfo();
        user.userInfo = userInfo;
        System.out.println(user);
        System.out.println(user.userInfo);
        try {
            User copy = (User) user.clone();
            System.out.println(copy);
            System.out.println(copy.userInfo);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
      //1.拷贝的User实例与原来不一样,是两个对象
    //com.javase.Class和Object.Object方法.用到的类.User@4dc63996
    //com.javase.Class和Object.Object方法.用到的类.UserInfo@d716361
      //2.而拷贝后对象的userinfo引用对象是同一个
    //com.javase.Class和Object.Object方法.用到的类.User@6ff3c5b5
    //com.javase.Class和Object.Object方法.用到的类.UserInfo@d716361
    
    //结合 1,2 所以这是浅拷贝
    }
    

深拷贝和浅拷贝的区别

  • 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着和原始对象属性值一模一样的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址(也就是说,这两个对象里面引用的对象是同一个,因为引用地址一样),因此如果其中一个对象改变了对象里面的其他引用地址,就会影响到另一个对象
  • 深拷贝会拷贝所有的属性,当对象和它里面所引用的对象一起被拷贝时就发生了深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
    • 要实现深拷贝,首先要实现 Clonable 接口,并重写 clone 方法,除了调用父类中的 clone 方法得到新的对象外, 还要将该对象中的引用变量也 clone 出来。如果只是用 Object 中默认的 clone 方法(即没有重写 clone 方法),那么就是浅拷贝

clone 与 new

  • new 操作符的本意是分配内存。程序执行到 new 操作符时, 首先去看 new 操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象
  • 而 clone 在第一步是和 new 相似的, 都是分配内存,调用 clone 方法时,分配一个和源对象(即调用 clone 方法的对象)相同大小的内存空间,然后再用原对象中对应的各个域,来填充新对象的域,填充完成之后,clone 方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部
  • 也就是说,一个对象在浅拷贝以后,只是把对象复制了一份放在堆空间的另一个地方,但是成员变量如果有引用指向其他对象,这个引用指向的对象和被拷贝的对象中引用指向的对象是一样的。当然,基本数据类型还是会重新拷贝一份的

4. getClass()

public final native Class<?> getClass()

  • getClass() 是一个 native 方法,返回主调对象对应的的类对象/运行时类对象 Class<?>。效果与 Object.class 相同
  • 在 Java 中,类是对具有一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例
  • 作为概念层次的类,其本身也具有某些共同的特性,如都具有类名称、由类加载器去加载,都具有包,具有父类,属性和方法等
  • 于是,Java 中又专门定义了一个类,Class,去描述其他类所具有的这些特性,因此,从此角度去看,类本身也都是属于 Class 类的对象。为与经常意义上的对象相区分,在此称之为"类对象"

5. equals()

public boolean equals(Object obj)

  • Object 类中的源码

    //Object 原生的 equals() 方法内部调用的正是 ==,与 == 具有相同的含义
    public boolean equals(Object obj) {  
         return (this == obj);  //比较地址值
    } 
    
  • 要使用 equals() 比较两个对象的内容是否一致,就需要在比较对象对应的类中去重写 equals() 方法,否则默认调用 Object 类中的 equals() 方法,即比较两个对象的地址值

6. hashCode()

public native int hashCode()

  • hashCode() 方法返回一个整形数值,表示该对象的哈希码值

  • hashCode() 的一些约定

    • 在 Java 应用程序程序执行期间,对于同一对象多次调用 hashCode() 方法时,其返回的哈希码是相同的,前提是将对象进行 equals 比较时所用的标尺信息未做修改。在 Java 应用程序的一次执行到另外一次执行,同一对象的 hashCode() 返回的哈希码无须保持一致
    • 如果两个对象相等,那么这两个对象调用 hashCode() 返回的哈希码也必须相等
    • 反之,两个对象调用 hashCode() 返回的哈希码相等,这两个对象不一定相等
    • 需要注意的是,如果两个对象的值相同 (重写的 equals 方法只比较了数值大小),那么他们的哈希值不一定相同
  • 重写 equals() 同时也要重写 hashCode()

    • 这主要体现在 hashCode() 方法的作用上,其主要用于增强哈希表的性能。在集合类中,以 Set 为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode() 方法,需要将 Set 进行一次遍历,并逐一用 equals() 方法判断两个对象是否相等,此种算法时间复杂度为 O(n)。通过借助于 hashCode 方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可
  • 对象的 hashCode() 返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode()相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode() 返回的哈希码可能相同

7. toString()

public String toString()

  • Object() 源码

    //toString() 方法返回该对象的字符串表示
    /*
    *getClass() 返回对象的类对象
    *getName() 以 String 形式返回类对象的名称(含包名)
    *Integer.toHexString(hashCode())则是以对象的哈希码为实参,以16进制无符号整数形式返回此哈希码*的字符串表示形式
    *如 u1 的哈希码是 638,则对应的 16 进制为 27e,调用 toString() 方法返回的结果为:*com.corn.objectsummary.User@27e
    */
     public String toString() {  
        return getClass().getName() + "@" + Integer.toHexString(hashCode());  
    }  
    
  • toString() 是由对象的类型其哈希码唯一确定,同一类型但不相等的两个对象分别调用 toString() 方法返回的结果可能相同

  • System.out.println(obj),其内部也是通过 toString() 来实现的

8. finalize()

protected void finalize()

  • finalize方法主要与Java垃圾回收机制有关

  • Object 中的源码

    protected void finalize() throws Throwable { }
    
  • 我们发现 Object 类中 finalize 方法被定义成一个空方法,为什么要如此定义呢?finalize 方法的调用时机是怎么样的呢?

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

9-11. wait()、wait(long timeout)、wait(long timeout,int nanos)

public final native void wait(long timeout) 是一个本地方法,其他两个 wait 方法里面都是调用了这个 wait 方法

  • Object 中 wait() 源码

    //其他带参的同理
    public final void wait() throws InterruptedException {  
         wait(0);  
    }
    
  • 调用此方法让当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的 notisfy()/notisfyAll() 方法,或超过指定的超时时间,或其他线程调用了 interrupt() 中断了该线程(此时会抛出一个 InterruptedException 异常)

12-13. notify()、notifyAll()

都是 public final native void 方法

  • 唤醒在此对象监视器上等待的单个线程/所有线程
  • wait(…) 与 notify() 或 notifyAll() 一般情况下都是配套使用

3. Class 类和 Object 类

  • java.lang.Object 是一个 Java 类,但并不是 java.lang.Class 的一个实例。后者只是一个用于描述 Java 类与接口的、用于支持反射操作的类型

  • java.lang.Class 是 java.lang.Object 的派生类,前者继承自后者

  • 其实这些相互依赖的核心类型完全可以在“混沌”中一口气都初始化好的,然后对象系统的状态才叫做完成了“bootstrap”,后面就可以按照 Java 对象系统的一般规则去运行。JVM、JavaScript、Python、Ruby等的运行时都有这样的 bootstrap 过程

    在“混沌”(boostrap过程)里,JVM 可以为对象系统中最重要的一些核心类型先分配好内存空间,让它们进入[已分配空间]但[尚未完全初始化]状态。此时这些对象虽然已经分配了空间,但因为状态还不完整,所以尚不可使用

    然后,通过这些分配好的空间把这些核心类型之间的引用关系串好。到此为止所有动作都由 JVM 完成,此时尚未执行任何 Java 字节码。然后这些核心类型就进入了[完全初始化]状态,对象系统就可以开始自我运行下去,也就是可以开始执行 Java字节码来进一步完成 Java 系统的初始化了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值