JDK源码阅读之java.lang.Object(第二章)
Posted 康子的自留地
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK源码阅读之java.lang.Object(第二章)相关的知识,希望对你有一定的参考价值。
今天这章介绍JDK中所有类的基类——java.lang.Object。
Object 类属于 java.lang 包,此包下的所有类在使用时无需手动导入,系统会在程序编译期间自动导入。Object类没有定义属性,一共有13个方法,具体的类定义结构如下图:
阅读笔记:
1、使用 java.lang 包下的所有类,都不需要手动导入,因为JVM虚拟机会默认自动导入java.lang包,这个包里面的类很常用,基本上你每写一个程序都要用到这个包里面的东西。你想啊,你基本每次写程序都得定义给一个基本数据类型吧,如果每次导入那得多麻烦啊。所以,jvm直接默认自动导入就大大减少工作量了。与此相似的:jsp的九大内置对象也是因为常用,所以就根本不用声明就可以用。
2、Java中的两种导包形式:
①、单类型导入(single-type-import),例如import java.util.Date
就是需要什么类便导入什么类,一般我们使用的编程工具默认都是按照单类型导包的
②、按需类型导入(type-import-on-demand),例如import java.util.*
可能看到后面的 *,大家会以为是导入java.util包下的所有类,其实并不是这样,编译器会根据名字按需导入,并不是导入整个包下的所有类,所以按需类型导入是绝对不会降低Java代码的执行效率的,但会影响到Java代码的编译速度。所以我们在编码时最好是使用单类型导入,这样不仅能提高编译速度,也能避免命名冲突。
3、类构造器public Object();
类构造器是创建Java对象的途径之一,通过 new 关键字调用构造器完成对象的实例化,还能通过构造器对对象进行相应的初始化。一个类必须要有一个构造器的存在,如果没有显示声明,那么系统会默认创造一个无参构造器,在JDK的Object类源码中,是看不到构造器的,系统会自动添加一个无参构造器。我们可以通过:
Object object = new Object();
构造一个Object类的对象。
注:如果给类定义了有参构造器,就必须手动写一个无参构造器
4、private static native void registerNatives();
registerNatives()方法本身,其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。
registerNatives函数前面有native关键字修饰,Java中,用native关键字修饰的函数表明该方法的实现并不是在Java中去完成,而是由C/C++去完成,
并被编译成了.dll,由Java去调用。方法的具体实现体在dll文件中,对于不同平台,其具体实现应该有所不同。用native修饰,即表示操作系统,需要提供此方法,Java本身需要使用。
源码中,此方法的声明后有紧接着一段静态代码块:
static {
registerNatives();
}
是因为registerNatives()修饰符为private,且并没有执行,通过这个静态代码块达到调用目的。
静态代码块就是一个类在初始化过程中必定会执行的内容,所以在类加载的时候是会执行该方法的,通过该方法来注册本地方法。
5、public final native Class<?> getClass();
native修饰的方法由操作系统帮我们实现。
getClass方法的作用是返回一个对象的运行时类,通过这个类对象我们可以获取该运行时类的相关属性和方法。
6、protected native Object clone() throws CloneNotSupportedException;
clone()方法又是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法,具体的实现是有C/C++完成的。clone英文翻译为"克隆",其目的是创建并返回此对象的一个副本。
举个例子:
队友有一把98k狙击枪,你看着不错,也想要个一样的。你调用此方法即可像变出一把一模一样的98k出来。配置一样。但从此刻起,原来的那把98k如果加了新的配件比如加了一个狙击镜,与你克隆出来的这把98k没有任何关系了。你克隆出来的对象变不变完全在于你对克隆出来的98k有没有进行过什么操作了。
7、public boolean equals(Object obj);
public boolean equals(Object obj) {
return (this == obj);
}
由代码可见Object原生的equals()方法内部调用的正是==,与==具有相同的含义,都是比较两个对象的引用是否相等,从另一方面来讲,如果两个对象的引用相等,那么这两个对象一定是相等的。
实际业务需求,对于User bean,由实际的业务需求可知当字段id相同时,表示的是同一个User,即两个User对象相等。
则可以重写equals以重定义User对象相等的标准。
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof User)) {
return false;
}
if (((User) obj).getId() == this.getId()) {
return true;
}
return false;
}
不过这个并不妥当,它破坏了Java中的约定:重写equals()方法必须重写hasCode()方法。
8、public native int hashCode();
这也是一个用 native 声明的本地方法,作用是返回 int 类型的数值,表示该对象的哈希码值。
hashCode()具有如下约定:
1).在Java应用程序程序执行期间,对于同一对象多次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未做修改。在Java应用程序的一次执行到另外一次执行,同一对象的hashCode()返回的哈希码无须保持一致;
2).如果两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等,所以重写equals()方法必须重写hasCode()方法;
3).反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不一定相等。
注:对于 Map 集合,我们可以选取Java中的基本类型,还有引用类型 String 作为 key,因为它们都按照规范重写了 equals 方法和 hashCode 方法。但是如果你用自定义对象作为 key,那么一定要覆写 equals 方法和 hashCode 方法,不然会有意想不到的错误产生。
9、public String toString();
toString()方法返回该对象的字符串表示。源码如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString()方法经常用到,即使没有显式调用,但当我们使用System.out.println(obj)时,其内部也是通过toString()来实现的。
getClass()返回对象的类对象,getClassName()以String形式返回类对象的名称(含包名)。Integer.toHexString(hashCode())则是以对象的哈希
码为实参,以16进制无符号整数形式返回此哈希码的字符串表示形式。
toString()是由对象的类型和其哈希码唯一确定,同一类型但不相等的两个对象分别调用toString()方法返回的结果可能相同。
10、wait(...) / notify() / notifyAll();
wait():调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法。
wait(long timeout)/wait(long timeout, int nanos):调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)
的notisfy()/notisfyAll()方法,或超过指定的超时时间量。
notify()/notifyAll():唤醒在此对象监视器上等待的单个线程/所有线程。
注:涉及到多线程暂不深究。
10、protected void finalize();
该方法用于垃圾回收,一般由 JVM 自动调用,一般不需要程序员去手动调用该方法。JVM准备对此对象所占用的内存空间进行垃圾回收前,将被调用。
以上是关于JDK源码阅读之java.lang.Object(第二章)的主要内容,如果未能解决你的问题,请参考以下文章
JDK源码阅读4-utilCollection-List---LinkedList