本文共 7070 字,大约阅读时间需要 23 分钟。
Object这些类的实现很多都调用了系统底层的本地方法,所以有必要在这里先说明一下关于本地方法的情况:1.本地方法的概念 本地方法是指用本地程序设计语言,比如:c或者c++,来编写的特殊方法。在java语言中通过native关键字来修饰,通过Java Native Interface(JNI)技术来支持java应用程序来调用本地方法,本地方法在本地语言中可以执行任意的计算任务,并返回到java程序设计语言中2.本地方法的用途从历史上看本地方法主要有三种用途: 1) 提供“访问特定于平台的机制”的能力,比如:访问注册表和文件锁2) 提供访问遗留代码库的能力,从而可以访问遗留数据3) 可以通过本地语言,编写应用程序中注重性能的部分,以提高系统的性能3.使用本地方法的优缺点优点:1) 访问特定于平台的机制2) 提高系统性能缺点:1) 本地代码中的一个bug就有可能破坏掉整个应用程序2) 本地语言是不安全的,所以,使用本地方法的应用程序也不能免受内存毁坏错误的影响3) 本地语言是与平台相关的,使用本地方法的应用程序也不再是可自由移植的4) 使用本地方法的应用程序更难调试5) 在进入和退出本地代码时,需要相关的固定开销,所以,如果本地代码时做的少量的工作,本地方法就可能降低性能6) 需要“胶合代码”的本地方法编写起来单调乏味,并且难以阅读
1.hashCode()方法
hashcode()方法用于JVM为Object对象分配的一个int类型的数值,用来提高对HashMap、HashTable等基于Hash值的对象存取的效率,hashcode()返回的并不是对象在内存中的地址。 Object对象在JDK10版本中位于C:\Program Files\Java\jdk-10\lib\src\java.base\java\lang目录下,这个目录与之前JDK项目结构不同,多出了这个java.base目录,目录下存放的源码分别为以java、com、javax、jdk、sun开头的源码包。 打开Object源码可以看到hashcode()定义如下:public native int hashCode();
可以看出该方法是一个本地方法,Java将调用本地方法库实现此方法,通过调用Object.c文件(需要下载并编译OpenJDK才可以):
static JNINativeMethod methods[] = { { "hashCode", "()I", (void *)&JVM_IHashCode},//hashcode的方法指针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},};
hashcode()方法被注册成由JVM_IHashCode方法指针来处理,wait()方法被注册成由JVM_MonitorWait方法指针来处理,notify()方法被注册成由JVM_MonitorNotify方法指针来处理,notifyAl()方法被注册成由JVM_MonitorNotifyAll方法指针来处理,clone()方法被注册成由JVM_Clone方法指针来处理。
JVM——IHashCode方法指针在jvm.cpp中定义,调用了ObjectSynchronizer::FastHashCode方法:JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle)) JVMWrapper("JVM_IHashCode"); // as implemented in the classic virtual machine; return 0 if object is NULL return handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;JVM_END
ObjectSynchronizer::fashHashCode()方法在 synchronizer.cpp 文件中实现。
补充1:System.java类中的IdentityHashCode()方法 这个方法底层调用的同样是JVM_IHashCode()方法–>ObjectSynchronizer::fashHashCode()方法, 这两个方法不同的地方在于:HashCode()和IdentityHashCode()方法原始实现都会基于对象的地址来生成hash值。由于所有类都继承了Object,所以具体类如果重写了hashcode()方法,那么就会调用子类中重写的方法,就不会依据对象的地址来生成hash值,那么值相同地址不同的对象得到的hash值就可能会相同,这就是我们在设计一些类中重写hashcode()和equals()方法的原因。 补充2: Effective Java中比较推荐的hashcode()重写方式://Lazily initialized, cached hashCode@Overridepublic int hashCode() { int result = hashCode(); if (result == 0) { result =17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; hashCode = result; } return result;}
2.equals()方法
equals()方法用于判断两个Object(子类)对象是否相等,这里的相等包含两个层面的概念,一是指这两个对象存储地址一致,那么这两个对象一定是同一个对象;二是指这两个对象值域相同,所以我们常说的==是比较地址相等,当比较对象是基本数据类型,比较变量的值;当比较对象是引用类型时,比较两个对象的引用地址是否相等,equals()方法比较值相等,那么这些说法是否正确呢?我们直接看代码实现:public boolean equals(Object obj) { return (this == obj); }
这是Object类中关于equals()方法的实现,直接用==比较对象地址是否相同,所以如果当前没有重写equals()方法,那么使用equals()方法和实现==的效果相同,对于基本的数据类型,包括String对象在内都已经重写了equals()方法:
这里是我个人的实现方式:@Overridepublic boolean equals(Object obj) { if(this == obj) { return true; } return obj instanceof(xxx) && ((xxx) obj).x == x && ((xxx) obj).y == y;}
补充1:
hashCode相等不代表==比较结果为true,IdentityHashCode相等==比较结果为true; 补充2: hashCode相等对象equals比较不一定为true(按照hashCode()方法上述的实现方式可以认为结果为true) 补充3: equals()方法是对对象内容的比较,所以在字符串比较过程中更多用equals()方法 3.clone()方法 clone()是用于对象拷贝,方法的访问限制关键字为protected,只有继承了Cloneable接口的对象类才可以调用被重写的clone()方法,步骤如下: I.继承Cloneable接口 II.重写clone()并捕获异常(如果当前类中包含非基本类型的属性则需要非基本类型对象中实现了重写clone()方法) III.调用clone()方法拷贝对象Object类中对clone()方法的声明:@HotSpotIntrinsicCandidateprotected native Object clone() throws CloneNotSupportedException;浅拷贝:return (xxx) super.clone();深拷贝:xxx result = (xxx) super.clone();result.elements = elements.clone();return result;Effective Java中对于深拷贝的实现:Entry deepCopy() { Entry result = new Entry(key,value,next); for(Entry e = result;e.next != null;e = e.next) { e.next = new Entry(e.next.key,e.next.value,e.next.next); } return result;}拷贝工厂类(效果等同于深拷贝,在Alibaba编程规约中要求):Myclass deepCopy() { Myclass result = new Myclass(); result.elements = elements; return result;}
4.toString()方法
toString()将字符串按照指定格式输出,便于人机交互 上源码:Object类中toString:public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode());}Integer类中toString:@HotSpotIntrinsicCandidatepublic static String toString(int i) { int size = stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; getChars(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; StringUTF16.getChars(i, size, buf); return new String(buf, UTF16); }}HashMap类中toString:public final String toString() { return key + "=" + value; }
Java应用程序中前端对于后端接口的调用往往是通过字符串传递,因此需要按照程序需要重写toString()方法
5.getClass()方法 getClass()方法用于对象获取类名,通过反射机制在运行期获取对象类型:Object类中getClass:@HotSpotIntrinsicCandidatepublic final native Class getClass();
而直接通过类名.class在编译器就可以获取对象类型
6.wait()方法 wait()方法用于在线程运行过程中释放对象锁,等待被唤醒或一定时间或获得对象锁继续运行,主要用于多线程程序中线程调度 为了更好的理解wait()方法,我们需要从线程运行状态入手来分析: 在线程状态转换图中,当当前线程调用了wait()方法后会进入到等待线程池,当被其他线程唤醒或者等待状态被打断,当前线程将被唤醒去尝试获取对象锁资源,然后由线程调度器 分配CPU资源得到执行。 补充1: Thread.sleep()方法同样可以让线程进入等待状态,但sleep()方法不会释放当前对象锁资源,这样在sleep()方法执行完成后,将可以直接进入待运行状态,获取CPU资源后就可以继续运行 补充2: 在Object类源码中,有3种wait()方法,分别为wait() & wait(long timeout) & wait(long timeout, int nanos),其中wait()和wait(long timeout, int nanos)都是调用wait(long timeout)方法wait()方法:public final void wait() throws InterruptedException { wait(0L);}wait(long timeout)方法:public final native void wait(long timeout) throws InterruptedException;wait(long timeout, int nanos)方法: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);}
7.notify() & notifyAll()方法
notify()和notifyAll()方法对应于wait()方法,wait()方法用于让运行在当前锁资源上的线程进入等待线程池,而notify()则将等待在当前所资源上的任意一个线程唤醒,notifyAll()将等待在当前所资源上的所有线程唤醒,并尝试获取锁资源(可能当前线程在调用notify()/notifyAll()方法之后还没有理解释放对象锁)等待线程调度器分配CPU资源进入运行状态。 上源码:notify()方法:@HotSpotIntrinsicCandidatepublic final native void notify();notifyAll()方法:@HotSpotIntrinsicCandidatepublic final native void notifyAll();
补充1:
notify()方法只唤醒一个等待(对象锁)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现;notifyAll()方法唤醒所有等待(对象锁)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll() 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。 补充2: notify()方法中没有被唤醒的其他竞争线程仍然等待,直至有新的notify ,notifyAll被调用;notifyAll()方法中所有被唤醒的线程不再等待,进入获取对象锁线程池。被调度处理的线程 synchronized 代码块执行完成后,之前所有被唤醒的线程都有可能获得该对象锁权限,这个由JVM算法决定。