嚼一嚼Object类中的方法


嚼一嚼Object类中的方法

Java 中的所有类都有一个共同的祖先,那就是 Object 类,现在我们就来看看这个类中有哪些方法。

1、Object 类中方法源码(JDK8)

private static native void registerNatives();

public final native Class<?> getClass();

public native int hashCode();

public boolean equals(Object obj) { return (this == obj);}

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();

public final native void wait(long timeout) throws InterruptedException;

    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 > 0) {
timeout++;
}

wait(timeout);
}

    public final void wait() throws InterruptedException {
wait(0);
}

protected void finalize() throws Throwable { }

2、registerNatives 方法

2.1、本地方法

在很多类中,都会像 Object 类一样,存在如下代码:

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

native 修饰的方法是本地方法,那么我们先弄清楚何为本地方法。

Java有两种方法:Java方法和本地方法。Java方法是由Java语言编写,编译成字节码,存储在class文件中。本地方法是由其他语言(比如C,C++,或者汇编)编写的,编译成和处理器相关的机器代码。本地方法保存在动态连接库中,格式是各个平台专有的。Java方法是平台无关的,但本地方法却不是。 运行中的Java程序调用本地方法时,虚拟机装载包含这个本地方法的动态库,并调用这个方法。本地方法是联系 Java 程序和底层主机操作系统的连接方法。

2.2、registerNatives 方法作用

这里的 registerNatives 就是一个本地方法,从方法名可以判断,这个方法是用来注册本地方法的。那么注册哪些本地方法呢,我猜你已经想到,就是 Object 类中的其他 native 方法。

一个 Java 程序要想调用一个本地方法,需要执行两个步骤:第一,通过 System.loadLibrary() 将包含本地方法实现的动态文件加载进内存;第二,当Java程序需要调用本地方法时,虚拟机在加载的动态文件中定位并链接该本地方法,从而得以执行本地方法。

Object 类中的 registerNatives 方法的作用就是,让程序主动将本地方法链接到调用方,当 Java 程序需要调用本地方法时,就不需要虚拟机定位链接,而是可以直接调用。

使用 registerNatives 方法的几点好处:

通过 registerNatives 方法在类被加载的时候就主动将本地方法链接到调用方,比当方法被使用时再由虚拟机来定位和链接更方便有效
如果本地方法在程序运行中更新了,可以通过调用 registerNative 方法进行更新
Java 程序需要调用一个本地应用提供的方法时,因为虚拟机只会检索本地动态库,因而虚拟机是无法定位到本地方法实现的,这个时候就只能使用 registerNatives 方法进行主动链接
通过 registerNatives 方法,在定义本地方法实现的时候,可以不遵守JNI命名规范

JNI 命名规范:

前缀:Java_
类的全限定名,用下划线进行分隔
方法名

例如 Java_java_lang_Object_registerNatives.

2.3、registerNatives 方法原理

传统 Java JNI 方式:

编写带有 native 方法的 Java 类
使用 javah 命令生成 .h 头文件
编写代码实现头文件中的方法

上述方式每次都需要通过 javah 依据 Java 类的全类名生成对应的 native 函数全名称,其实可以使用 registerNatives 方法把 C/C++ 中的方法隐射到 Java 中的 native 方法。

贴上 Object 类的中 registerNatives 方法的 C 语言实现:

/**
第一个参数:hashCode 是java中的方法名称
第二个参数:()I 是java中方法的签名,可以通过javap -s -p 类名.class 查看
第三个参数:(void *)&JVM_IHashCode (返回值类型)映射到 native 的方法名称
*/
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
// 注册本地方法
(*env)->RegisterNatives(env, cls,
methods, sizeof(methods)/sizeof(methods[0]));
}

代码中的 *env 为 JNI 环境,RegisterNatives 方法即是进行动态注册。

3、getClass 方法

返回当前对象的运行时类,即一个 Class.

Class 是用来描述字节码的,是一个描述类的类。

4、hashCode 方法

hashCode 方法是根据一定的规则,将对象相关的信息映射成一个数值,这个数值称为散列值。

hashCode 方法的主要作用是为了配合基于散列的集合一起正常运行,例如 HashSet、HashMap、HashTable等。

贴一下 Object 中 hashCode 的本地方法实现:

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;
}

总的来说有 6 种生成 hashCode 的方式 ,JDK6 和 JDK7 中使用方式 1 随机数的形式,hash 值是存在对象头中的,所以多次调用也不会出现不一致的情况。JDK8 使用方式 5,走程序的 else 条件,即使用 Xorshift.

5、equals 方法

判断两个对象是否等价,返回 true 表示等价,返回 false 表示不等。

Object 类中是直接使用 == 关系运算符进行比较,== 运算符比较可分为两种情况:

基本数据类型:比较值
引用数据类型:比较内存中的存放地址

显然对于引用数据类型,Object 中的 equals 方法并不太适用,所以一般会进行重写,下面贴一下 String 中的 equals 方法源码:

    public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

可以看到,String 是先比较内存地址,内存地址不同的情况下,逐个字段进行比较,这样使用 equals 比较的结果更符合逻辑。

需要注意的是,当 equals 方法被重写时,通常都要重写 hashCode 方法,使之满足以下规律:

equals 为 true ==> hashCode 相同
equals 为 false ==> hashCode 不同
hashCode 相同 ==> equals 未知
hashCode 不同 ==> equals 为 false

6、clone 方法

clone 的意思是复制,顾名思义,这个方法用作对象的复制。

对象复制,或者称之为拷贝,可分为两种,深拷贝和浅拷贝:

深拷贝:假设 B 复制了 A,当修改 A 时,任何情况下 B 不会发生变化,这就是深拷贝
浅拷贝:修改 A 时 B 也会发生变化,这就是浅拷贝

深拷贝与浅拷贝出现的根源在于引用类型,浅拷贝时,被复制的对象的所有变量与原来对象的值相同,但是引用类型的变量,仍然指向原来的对象,即对象中的对象,其实是共享的。深拷贝是一个独立的对象拷贝,从最外层的对象到最里面的引用对象,都复制一遍。所以深拷贝速度较慢且花销较大。

Object 中的 clone 方法是浅拷贝。

7、toString 方法

数据对象的信息,这里需要注意一点,@ 后面的十六进制数字,并不是内存地址,只是 hashCode 的十六进制形式。

8、wait 和 notify 方法

先了解两个概念,锁池和等待池:

锁池:假设线程 A 已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个 synchronized 方法(或者 synchronized 块),由于这些线程在进入对象的 synchronized 方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程 A 拥有,所以这些线程就进入了该对象的锁池中(锁释放后去竞争)
等待池:假设一个线程 A 调用了某个对象的 wait 方法,线程 A 就会释放该对象的锁,进入到该对象的等待池中(不会竞争对象锁)

理解了锁池和等待池,那么 wait 方法的作用也就很清晰了,是将已经获得对象锁的线程立即移到等待池中。

再来说说 notify 的作用:

作用对象:调用 a 对象的 wait 方法后,线程处于 a 对象的等待池中,等待池中的线程不会竞争 a 对象的锁。notify 作用的对象就是这些线程
作用结果:调用 notify 后会有一个线程由等待池进入锁池,参与锁的竞争(随机唤醒)。而 notifyAll 会将对象等待池中的所有对象移到锁池中
作用时机:当调用 notify 或 notifyAll 的线程执行完同步方法或者同步代码块中的所有代码,才会释放这把锁

下面说说需要注意的点:

调用 a.wait() 或者 a.notify() 前必须要先拿到 a 对象的锁,故 wait 和 notify 方法要放在该对象的同步方法或者步代码块中
wait 方法会立即释放锁、释放 CPU
notify 方法不会立即释放锁,需等 notify 所在线程执行完同步块
未获得该对象锁时调用该对象的 wait 或 notify 方法,会抛出 java.lang.IllegalMonitorStateException 异常
yield、sleep 不会释放锁,但会释放 CPU
join 释放锁,不会释放CPU
wait、sleep、join 都可以通过 interrupt 打断线程暂停状态,使线程立即抛出 InterruptedException

聊聊 Thread 中的 join 方法:

作用:假如在 main 线程调用 t.join(),那么 main 线程阻塞,直到 t 线程运行结束

原理:查看源码,发现 join 方法是同步方法,在内部使用了 wait 方法。当 main 线程调用 t.join() 时,先获取到了 t 线程对象的锁,然后在 join 方法内部执行 wait 方法,导致 main 线程阻塞(因为是 main 线程间接调用了 wait 方法,而不是 t 线程中调用的)。而结束阻塞的方式,是 t.isAlive() 结果返回 false,即线程已经是不可用状态如已经运行结束

9、wait(long timeout) 方法

与 wait 方法的区别是,此方法可以指定一个超时,超时之后将自己唤醒线程。使用 notify 或 notifyAll 可以在超时前唤醒线程。需要注意的是,wait(0) 与 wait() 是等价的。

wait(long timeout,int nanos)这是另一个提供相同功能的方法,唯一的区别是这个可以提供更高的精度。如果 nanos 在 [0,1000000) 之间,那么超时时间为 timeout+1.

10、finalize 方法

当垃圾回收器要回收对象所占内存之前,会先调用该对象的 finalize 方法
finalize 方法只会在对象被回收前调用一次,调用具有不确定性,只保证方法会被调用,但是不保证会等待它运行完,原因是如果 finalize 方法运行缓慢或者发生死循环,会导致内存回收系统崩溃
finalize 方法是对象逃脱被回收的最后一次机会,并且最多只能用一次
不建议使用 finalize 方法,它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序

参考

https://blog.csdn.net/Saintyyu/article/details/90452826
https://blog.csdn.net/j3T9Z7H/article/details/107852624
https://blog.csdn.net/djzhao/article/details/79410229
https://baijiahao.baidu.com/s?id=1655232869611610920&wfr=spider&for=pc

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

嚼一嚼Object类中的方法

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » 嚼一嚼Object类中的方法
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏