最近在整理final相关的测试用例,所以对final进行了重新思考,final 可以修饰类、方法、实例、参数!
final修饰实例时,表示该实例不可以被修改,但是具体是指实例的内存地址不可以被修改? 还是实例里的值不可以被修改?还是实例的内存地址和值都不可以被修改?
其实是蛮简单的一个问题,当final修饰到对象实例时, 表示什么意义,你有真正思考过吗?
所以我写了一个测试类:
import com.alibaba.fastjson.JSONObject;
/**
* final 修饰对象
*
* @author zhoufy
* @date 2019年1月24日 下午6:35:26
*/
public class FinalTest {
public static void main(String[] args){
FinalTest t = new FinalTest();
t.test();
}
private void test(){
final Inner inner = new Inner();
print(inner);
inner.setId(1);
inner.setName("xiao ming");
print(inner);
inner.setId(2);
inner.setName("xiao hong");
print(inner);
}
private void print(Inner inner){
System.out.println(inner.hashCode()+" : "+System.identityHashCode(inner)+" : "+JSONObject.toJSONString(inner));
}
class Inner{
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
输出:
366712642 : 366712642 : {“id”:0}
366712642 : 366712642 : {“id”:1,“name”:“xiao ming”}
366712642 : 366712642 : {“id”:2,“name”:“xiao hong”}
走到这里就已经证明了一点:final修饰的对象里的值是可以改变的,那地址呢? 而且还引起了我思考的一个问题haseCode()方法跟System.identityHashCode() 能代表对象的内存地址吗?
先看一下haseCode()的源码:
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
*
* The general contract of {@code hashCode} is:
*
* - Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
*
- If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
*
- It is not required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code hashCode} method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
*
*
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java™ programming language.)
* * @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();
这其中有几句话要细细斟酌:
当看到这已经知道了hashCode()方法返回值不能代表对象的内存地址!!!
顺便再看一下identityHashCode()方法的源码:
/**
* Returns the same hash code for the given object as
* would be returned by the default method hashCode(),
* whether or not the given object's class overrides
* hashCode().
* The hash code for the null reference is zero.
*
* @param x object for which the hashCode is to be calculated
* @return the hashCode
* @since JDK1.1
*/
public static native int identityHashCode(Object x);
从方法注释已经看出来,该方法默认返回的跟hashCode()方法同样的值
如果查看jdk的源码:
java.lang.System.java类对应的本地c语言的实现——System.c,在 openjdk-8u\jdk\src\share\native\java\lang 目录下
JNIEXPORT jint JNICALL
Java_java_lang_System_identityHashCode(JNIEnv *env, jobject this, jobject x)
{
return JVM_IHashCode(env, x);
}
java.lang.Object.java类对应的本地c语言的实现——Object.c,在 openjdk-8u\jdk\src\share\native\java\lang 目录下
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},
};
看jdk源说明hashCode()和identityHashCode() 调用的是同一个方法:JVM_IHashCode()
所以两个方法返回的值相等就对了!
那如何获取到Java对象的地址呢? 几经查询资料,原来还是有办法的,要通过sun.misc.Unsafe类来实现:
package com.zhoufy.sun.unsafe.e3;
public class A {
private long a; // not initialized value
public A() {
this.a = 1; // initialization
}
public long a() { return this.a; }
}
package com.zhoufy.sun.unsafe.e3;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
/**
* @author zhoufy
* @date 2019年2月2日 上午10:29:15
*/
@SuppressWarnings("restriction") //取消显示的警告集
public class ObjectsAddressDemo {
static final Unsafe unsafe = getUnsafe();
static final boolean is64bit = true; // auto detect if possible.
public static void main(String... args) {
A a = new A();
//GC前
System.out.println("-----------GC前------------");
print(a);
System.gc();
//GC后
System.out.println("-----------GC后------------");
print(a);
}
private static void print(A a) {
// hashcode
System.out.println("Hashcode : " + a.hashCode());
System.out.println("Hashcode : " + System.identityHashCode(a));
System.out.println("Hashcode (HEX) : " + Integer.toHexString(a.hashCode()));// Integer.toHexString(int)是将一个整型转成一个十六进制数
// toString
System.out.println("toString : " + String.valueOf(a));
//通过sun.misc.Unsafe;
printAddresses("Address", a);
}
@SuppressWarnings("deprecation")
public static void printAddresses(String label, Object... objects) {
System.out.print(label + ": 0x");
long last = 0;
int offset = unsafe.arrayBaseOffset(objects.getClass());
int scale = unsafe.arrayIndexScale(objects.getClass());
switch (scale) {
case 4:
long factor = is64bit ? 8 : 1;
final long i1 = (unsafe.getInt(objects, offset) & 0xFFFFFFFFL) * factor;
System.out.print(Long.toHexString(i1));
last = i1;
for (int i = 1; i < objects.length; i++) {
final long i2 = (unsafe.getInt(objects, offset + i * 4) & 0xFFFFFFFFL) * factor;
if (i2 > last)
System.out.print(", +" + Long.toHexString(i2 - last));
else
System.out.print(", -" + Long.toHexString(last - i2));
last = i2;
}
break;
case 8:
throw new AssertionError("Not supported");
}
System.out.println();
}
private static Unsafe getUnsafe() {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
输出:
-----------GC前------------
Hashcode : 1829164700
Hashcode : 1829164700
Hashcode (HEX) : 6d06d69c
toString : com.zhoufy.sun.unsafe.e3.A@6d06d69c
Address: 0x76b4635b8
-----------GC后------------
Hashcode : 1829164700
Hashcode : 1829164700
Hashcode (HEX) : 6d06d69c
toString : com.zhoufy.sun.unsafe.e3.A@6d06d69c
Address: 0x6c1a0a338
当然也可以通过jol工具包里的工具类实现获取对象的内存信息,使用jol工具包需要在maven里引入:
org.openjdk.jol
jol-core
0.9
Java例子:
package com.zhoufy.sun.unsafe.e3;
import java.lang.reflect.Field;
import org.openjdk.jol.info.GraphLayout;
import org.openjdk.jol.vm.VM;
import sun.misc.Unsafe;
/**
* @author zhoufy
* @date 2019年2月2日 上午10:29:15
*/
@SuppressWarnings("restriction") //取消显示的警告集
public class ObjectsAddressDemo {
static final Unsafe unsafe = getUnsafe();
static final boolean is64bit = true; // auto detect if possible.
public static void main(String... args) {
A a = new A();
//GC前
System.out.println("-----------GC前------------");
print(a);
System.gc();
//GC后
System.out.println("-----------GC后------------");
print(a);
}
private static void print(A a) {
//通过sun.misc.Unsafe;
printAddresses("Address", a);
//通过jol工具包打印对象的地址
System.out.println(GraphLayout.parseInstance(a).toPrintable());
System.out.println("Current address: " + VM.current().addressOf(a));
}
@SuppressWarnings("deprecation")
public static void printAddresses(String label, Object... objects) {
System.out.print(label + ": 0x");
long last = 0;
int offset = unsafe.arrayBaseOffset(objects.getClass());
int scale = unsafe.arrayIndexScale(objects.getClass());
switch (scale) {
case 4:
long factor = is64bit ? 8 : 1;
final long i1 = (unsafe.getInt(objects, offset) & 0xFFFFFFFFL) * factor;
System.out.print(Long.toHexString(i1));
last = i1;
for (int i = 1; i < objects.length; i++) {
final long i2 = (unsafe.getInt(objects, offset + i * 4) & 0xFFFFFFFFL) * factor;
if (i2 > last)
System.out.print(", +" + Long.toHexString(i2 - last));
else
System.out.print(", -" + Long.toHexString(last - i2));
last = i2;
}
break;
case 8:
throw new AssertionError("Not supported");
}
System.out.println();
}
private static Unsafe getUnsafe() {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
输出:
-----------GC前------------
Address: 0x76b463440
com.zhoufy.sun.unsafe.e3.A@119d7047d object externals:
ADDRESS SIZE TYPE PATH VALUE
76b463440 24 com.zhoufy.sun.unsafe.e3.A (object)
Current address: 31864534080
-----------GC后------------
Address: 0x6c1a0aab0
com.zhoufy.sun.unsafe.e3.A@119d7047d object externals:
ADDRESS SIZE TYPE PATH VALUE
6c1a0aab0 24 com.zhoufy.sun.unsafe.e3.A (object)
Current address: 29018335920
从输出信息来看,printAddresses()方法和jol工具类打印的对象的内存地址是一致的,当然如果去深入研究jol工具类,它底层也是通过sun.misc.Unsafe实现的!
原文链接:https://blog.csdn.net/zhoufanyang_china/article/details/86750351