JDK源码学习(一)---Object

目录

一 、简介

二、代码解析

三、为什么java.lang包下的类不需要手动导入

四、hashCode方法

1、hashCode方法的作用

2、hashCode 要求

3、hashCode 编写指导


一 、简介

java.lang.Object,是java所有类的父类。编写一个类的时候会默认的添加一个Object为父类。

jdk6以前,编译后zclass字节码中可以显示的看到extends Object。

二、代码解析

public class Object {
    //native修饰的方法都是本地方法,即在C/C++在DLL中已经实现,通过JNI调用
    private static native void registerNatives();
    //类初始化调用此方法
    static {
        registerNatives();
    }

    /**
     * Constructs a new object.
     */
    @HotSpotIntrinsicCandidate
    public Object() {}

    /**
     * 返回此Object的运行时类(每个类的Class类对象)
     * 每一个类在被加载的时候,都会生成一个Class类实例,
     * 而这个方法就可以在运行时期获得对象(这里的对象是堆里的那个对象,也就是获得的是动态类型的那个类)的Class对象,Class对象主要用于反射。
     */
    @HotSpotIntrinsicCandidate
    public final native Class<?> getClass();

    /**
     * 这个方法返回对象的哈希码值,这个方法与哈希表的性能有关,它的实现方法是通过将对象在内存中所处于的位置转换成数字,这个数字就是hashCode
     */
    @HotSpotIntrinsicCandidate
    public native int hashCode();

    /**
     * 对比两对象的内存地址,如果不重写,equals方法比较的是对象地址
     * 判断其它对象是否与此对象“相等”;如果一个对象要重写这个方法,需要与其它非空对象满足等价关系:以下的x,y,z均为非空对象
     * (1)自反性,x.equals(x)=true;
     * (2)对称性,x.equals(y) = y.equals(x);
     * (3)传递性,若x.equals(y) = true,y.equals(z) = true,则x.equals(z) = true;
     * (4)一致性,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回相同结果;
     * (5)x.equals(null) = false;
     * 注意: 当重写equals方法时,也要重写hashCode方法,这是为了确保相等的对象有相同的哈希码值
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }

    /**
     * protected方法,子类可重写,创建并返回此对象的一个副本。
     * 所有的数组都被视为实现接口 Cloneable,且 数组类型T[]的 clone方法返回的类型是T[] ,T 可以是任何引用或基本类型
     * 一个类只有实现了Cloneable接口(标记接口,无任何内容,只针对clone方法),才可以在该类的实例上调用clone方法,否则会抛出CloneNotSupportException。
     * Object中默认的实现是一个浅拷贝,也就是表面拷贝,如果需要实现深层次拷贝的话,必须对类中可变域生成新的实例
     */
    @HotSpotIntrinsicCandidate
    protected native Object clone() throws CloneNotSupportedException;

    /**
     * 返回对象的的字符串表示,默认是:类名+@+此对象哈希码的无符号十六进制表示组成,即值等于:getClass().getName() + '@' + Integer.toHexString(hashCode())
     */
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    /**
     * 唤醒在此对象监视器上等待的线程,如果有多个,随机选择一个
     */
    @HotSpotIntrinsicCandidate
    public final native void notify();

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

    /**
     * final修饰,不可重写,永久等待,直到被唤醒
     */
    public final void wait() throws InterruptedException {
        wait(0L);
    }

    /**
     * 本地已实现方法,在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待
     * 在超过给定参数 timeoutMillis 时间之前会一直等待,可提前被唤醒,即等个最大等待时间,如果是0 则永久等待
     */
    public final native void wait(long timeoutMillis) throws InterruptedException;

    /**
     * 与上面方面相同,但是可以精确控制时间到 毫微妙级别
     * timeoutMillis - 毫秒
     * nanos - 毫微妙
     * 1秒=1000豪秒 1毫秒=1000微秒 1微秒=1000毫微秒
     */
    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        }
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                    "nanosecond timeout value out of range");
        }
        if (nanos > 0) {
            timeoutMillis++;
        }
        wait(timeoutMillis);
    }

    /**
     * finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法,jdk9之后已被废弃
     * 一个对象只会调用一次
     */
    @Deprecated(since="9")
    protected void finalize() throws Throwable {}
}

三、为什么java.lang包下的类不需要手动导入

使用 java.lang 包下的所有类,都不需要手动导入。

  另外我们介绍一下Java中的两种导包形式,导包有两种方法:

  ①、单类型导入(single-type-import),例如import java.util.Date

  ②、按需类型导入(type-import-on-demand),例如import java.util.*

  单类型导入,需要什么类便导入什么类,这种方式是导入指定的public类或者接口;

  按需类型导入,比如 import java.util.*,可能看到后面的 *,我们根据名字按需导入要知道他是按照需求导入,并不是导入整个包下的所有类。

  Java编译器会从启动目录(bootstrap),扩展目录(extension)和用户类路径下去定位需要导入的类,而这些目录仅仅是给出了类的顶层目录,编译器的类文件定位方法大致可以理解为如下公式: 

1

顶层路径名 \ 包名 \ 文件名.class = 绝对路径

 

四、hashCode方法

哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率。在Java的Object类中有一个方法:

public native int hashCode();

 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现。

1、hashCode方法的作用

基本上都会涉及到hashCode。在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。

HotSpot JVM中生成hash散列值的实现(该实现位于hotspot/src/share/vm/runtime/synchronizer.cpp文件下):

static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  if (hashCode == 0) {
     // This form uses an unguarded global Park-Miller RNG,
     // so it's possible for two threads to race and generate the same RNG.
     // On MP system we'll have lots of RW access to a global, so the
     // mechanism induces lots of coherency traffic.
     value = os::random() ;
  } else
  if (hashCode == 1) {
     // This variation has the property of being stable (idempotent)
     // between STW operations.  This can be useful in some of the 1-0
     // synchronization schemes.
     intptr_t addrBits = intptr_t(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  } else
  if (hashCode == 2) {
     value = 1 ;            // for sensitivity testing
  } else
  if (hashCode == 3) {
     value = ++GVars.hcSequence ;
  } else
  if (hashCode == 4) {
     value = intptr_t(obj) ;
  } else {
     // Marsaglia's xor-shift scheme with thread-specific state
     // This is probably the best overall implementation -- we'll
     // likely make this the default in future releases.
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;
  }
 
  value &= markOopDesc::hash_mask;
  if (value == 0) value = 0xBAD ;
  assert (value != markOopDesc::no_hash, "invariant") ;
  TEVENT (hashCode: GENERATE) ;
  return value;
}

 

2、hashCode 要求

  ①、在程序运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。

  ②、通过equals调用返回true 的2个对象的hashCode一定一样。

  ③、通过equasl返回false 的2个对象的散列码不需要不同,也就是他们的hashCode方法的返回值允许出现相同的情况。

  因此我们可以得到如下推论:

  两个对象相等,其 hashCode 一定相同;

  两个对象不相等,其 hashCode 有可能相同;

  hashCode 相同的两个对象,不一定相等;

  hashCode 不相同的两个对象,一定不相等;

   这四个推论通过上图可以更好的理解。

  可能会有人疑问,对于不能重复的集合,为什么不直接通过 hashCode 对于每个元素都产生唯一的值,如果重复就是相同的值,这样不就不需要调用 equals 方法来判断是否相同了吗?
  实际上对于元素不是很多的情况下,直接通过 hashCode 产生唯一的索引值,通过这个索引值能直接找到元素,而且还能判断是否相同。比如数据库存储的数据,ID 是有序排列的,我们能通过 ID 直接找到某个元素,如果新插入的元素 ID 已经有了,那就表示是重复数据,这是很完美的办法。但现实是存储的元素很难有这样的 ID 关键字,也就很难这种实现 hashCode 的唯一算法,再者就算能实现,但是产生的 hashCode 码是非常大的,这会大的超过 Java 所能表示的范围,很占内存空间,所以也是不予考虑的。

  3、hashCode 编写指导

  ①、不同对象的hash码应该尽量不同,避免hash冲突,也就是算法获得的元素要尽量均匀分布。

  ②、hash 值是一个 int 类型,在Java中占用 4 个字节,也就是 232 次方,要避免溢出。

  在 JDK 的 Integer类,Float 类,String 类等都重写了 hashCode 方法,我们自定义对象也可以参考这些类来写。

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值