Object:
一. Object类是何时被织入其他的类中的
问题来源 : java中所有的类都可以直接调用Object类中的方法,IDEA在开发过程中就可以直接编码调用.我们也说Object是所有类的父类,但是代码中我们并没有extends Object
,那Object是何时织入的呢:
可能的答案: A. javac 编译进去的 B.jvm中执行时织入的 C.开发工具如何做到的???
JDK 6(含)之前是 编译器 处理, JDK 7(含)之后是虚拟机 处理. 我们可以用反汇编证明这一结果,用javap执行 *.class 获得反汇编的代码观察类是否继承了Object即可
结论:
1、JDK 6(含)之前是 编译器 处理
2、JDK 7(含)之后是 虚拟机 处理
3、IDEA里面是开发工具做了智能处理
二.Object类主要方法源码剖析 (jdk8)
package java.lang;
public class Object {
// native方法,对象初始化自动调用
private static native void registerNatives();
static {
registerNatives();
}
// native方法
public final native Class<?> getClass();
// native方法
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
// native方法
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
// native方法
public final native void notify();
// native方法
public final native void notifyAll();
// native方法
public final native void wait(long timeout) throws InterruptedException;
// 最终调用wait()
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);
}
// 最终调用wait()
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }
}
可以看出调用本地方法栈 7个方法(调用底层C++方法) , java方法 5个
1.public native int hashCode();
1.hashCode是什么: hashCode是通过调用native方法中C++代码的一个 get_next_hash()
方法获取的,是马萨利亚教授写的xor-shift 随机数算法(异或随机算法) ,因此问题2答案是:否
2.hashcode是不是内存地址: 否
3.hashcode存在哪里: 在对象头里,入下图. Mark Word中存储的就有hashCode.
其中,Mark Word的结构如下
从上图可以看出:
A.无锁状态:hashCode存放在Mark Word中
B.轻量级锁状态:Mark Word 存储的是一个锁记录的指针,指针指向C中的的 helper,从中可以获取到hashCode
C.重量级锁状态:Mark Word 存储的是一个锁记录的指针,指针指向C中的的 monitor,从中可以获取到hashCode
D.GC状态:GC结束后再根据锁状态获取hashCode,不会发生改变
E:偏向锁状态:一旦调用hashcode,偏向锁将被撤销,回到A状态(参考 https://blog.csdn.net/wo541075754/article/details/115497869
)
4.hashcode是否会发生改变: 可以从两方面去分析. 第一:有很多类需要用到hashCode进行一些 相等比较或者 计算索引下标,例如 hashSet,hashMap等,如果同一个对象的hashCode发生了改变,那对象的属性就会发生改变,肯定是不允许的 第二:hashCode的计算其实是基于算法实现.当hashCode方法未被调用时,object header中用来存储hashcode的位置为0,只有当hashCode方法首次被调用时,才会计算对应的hashcode值,并存储到object header中。当再次被调用时,则直接获取计算好hashcode即可。 因此即使发生了GC,对象的内存地址发生了改变,hashCode要么还没计算出来,要么计算出来已经存好不会发生改变.
// hashcode生成的方式
不同的JVM对hashcode值的生成方式不同。Open JDK中提供了6中生成hash值的方法。
0:随机数生成器(A randomly generated number.);
1:通过对象内存地址的函数生成(A function of memory address of the object.);
2:硬编码1(用于敏感度测试)(A hardcoded 1 (used for sensitivity testing.));
3:通过序列(A sequence.);
4:对象的内存地址,强制转换为int。(The memory address of the object, cast to int.)
5:线程状态与xorshift结合(Thread state combined with xorshift);
其中在OpenJDK6、7中使用的是随机数生成器的(第0种)方式,OpenJDK8、9则采用第5种作为默认的生成方式。所以,单纯从OpenJDK的实现来说,其实hashcode的生成与对象内存地址没有什么关系。而Object类中hashCode方法上的注释,很有可能是早期版本中使用到了第4种方式。
5.hashcode什么时候产生: 当hashCode方法首次被调用时
6.hashcode有什么用:
A:用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的
B:用于比较两个对象是否相同,和equals结合使用
7.hashcode值是唯一的吗: 不是唯一,因此hashCode相同的对象不一定相同,也是因此很多业务场景需要重写hashCode()和 equasl()两个方法
2.public boolean equals(Object obj){}
1.equals 比较的是什么
public boolean equals(Object obj) {
return (this == obj);
}
从上面Object.equals() 方法中可以看出,未重写equals方法时,java对象的equals方法即调用 ==
2.什么时候要重写equals方法
简单来说,我们可以按照自己的需求去重写equals方法,拿String.Class
举例
public boolean equals(Object anObject) {
// == 比较内存地址相同,返回true
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;
}
3.什么时候要重写hashCode方法
hashCode是给java集合类的一些动作提供支撑,来判断俩对象“是否是同一个”的标准,例如hashSet,hashMap等
4.为什么推荐equals和hashCode方法都要重写
有时候我们业务需要重写eqals
方法,但是有的框架底层用hashCode
实现了一些比较,为了保持一致性需要两个都重写
5.关于==
比较
用于基本数据(8种)类型(或包装类型)相互比较,比较二者的值是否相等。
用于引用数据(类、接口、数组)类型相互比较,比较二者地址是否相等。
public class Intern {
public static void main(String[] args) {
String str1 = "张三"; // 字符串常量池
String str2 = new String("张三"); // 堆内存
System.out.println("str1与str2是否相等>>" +(str1==str2)); // false(堆内存地址和字符串常量池地址进 行比较)
System.out.println("str1与str2是否相等>>" +(str1==str2.intern())); // true(String.intern()指向 了字符串常量池)
}
}
特殊情况, Value.of()
方法:
基础类和包装类进行比较时,会进行拆箱操作,实际比较的是值;
但是 Integer.value()
方法比较特殊
public static Integer valueOf(int i) {
// -128~127之间会从cache直接获取,超出这个范围的会new Intger(),因此
// Integer(127) == Integer(127) 是ture, Integer(128) == Integer(128) 是false
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
未完待续…