引用类型?常量池?包装类缓存?举个栗子
测试用的实体类
@Data
@Accessors(chain = true)
class Student{
private Integer no;
private String name;
private String lesson;
}
将一个map中的值取出来赋值给变量,此变量更改,这个map也会更改
//存放对象的map
Map<String, Object> hashMap = new HashMap<>();
//新建一个studentMap存信息
Map<Object, Object> studentMap = new HashMap<>();
studentMap.put("sname", "hi");
studentMap.put("sno", 1);
studentMap.put("lesson", "Python");
//放到map中
hashMap.put("hiObj",studentMap);
//新建一个学生对象并也放入这个map吗
Student student = new Student().setNo(1).setName("OO").setLesson("C++");
hashMap.put("stu", student);
//修改之前
System.out.println("更改之前:"+hashMap.get("hiObj"));
System.out.println("更改之前:"+hashMap.get("stu"));
System.out.println("更改之前:"+hashMap);
//将key为"hiObj"的studentMap取出来
Map<Object, Object> hiObj = (Map<Object, Object>) hashMap.get("hiObj");
hiObj.put("sname", "HELLO");
hiObj.put("sno", 3);
hiObj.put("lesson", "JavaScript");
student.setLesson("Java").setName("HH").setNo(2);
//修改之后
System.out.println("更改之后:"+hashMap.get("hiObj"));
System.out.println("更改之后:"+hashMap.get("stu"));
System.out.println("更改之后:"+hashMap);
console:
更改之前:{sno=1, sname=hi, lesson=Python}
更改之前:Student(no=1, name=OO, lesson=C++)
更改之前:{stu=Student(no=1, name=OO, lesson=C++), hiObj={sno=1, sname=hi, lesson=Python}}
更改之后:{sno=3, sname=HELLO, lesson=JavaScript}
更改之后:Student(no=2, name=HH, lesson=Java)
更改之后:{stu=Student(no=2, name=HH, lesson=Java), hiObj={sno=3, sname=HELLO, lesson=JavaScript}}
- hashMap.get("hiObj")是把地址赋值给变量,指针地址所指的对象只有一个,本质上都是对一个对象的修改,只是不同的变量在引用它,hashMap.get("stu")同理。
字符串对象的引用
- 怎么证明字符串常量和new 一个字符串对象的地址差异呢
String json = new String("Json");
System.out.println("json : "+json.hashCode());
String json2 = new String("Json");
System.out.println("json2 : "+json2.hashCode());
System.out.println("test addr1;"+"Cola".hashCode());
System.out.println("test addr2;"+"Cola".hashCode());
console:
json : 2318600
json2 : 2318600
test addr1;2106113
test addr2;2106113
- 很明显,由于String类型的hashCode()重写之后只要是相同的字符串hashCode就会相同,所以看不出地址差异
- 这里用System的native方法System.identityHashCode(),这个方法不管对象是否重写了hashCode()都是取的物理内存的地址值
String json = new String("Json");
System.out.println("json : "+System.identityHashCode(json));
String json2 = new String("Json");
System.out.println("json2 : "+System.identityHashCode(json2));
System.out.println("test addr1;"+System.identityHashCode("Cola"));
System.out.println("test addr2;"+System.identityHashCode("Cola"));
console;
json : 939047783
json2 : 1237514926
test addr1;548246552
test addr2;548246552
- 这里就可以看得出,new一个字符串对象即使字符串相同但是地址是不一样的,但是直接使用字面量赋值的地址是一样的,因为指向的是常量池的同一内存地址
- 我没想通上面map的例子的时候,觉得它只是把“hiObj”的地址拷贝赋值给了hiObj这个map变量,它的改变不会影响原来的hashMap的内部的对象状态,就像下面这个例子
Student student = new Student().setNo(new Integer(128)).setName(new String("Json")).setLesson("C++");
System.out.println("更改之前:" + student);
System.out.println("student.getName() addr : " + System.identityHashCode(student.getName()));
String sname = student.getName();
System.out.println("sname addr :" + System.identityHashCode(sname));
sname += "hui";
System.out.println("after sname addr : " + System.identityHashCode(sname));
System.out.println("after student.getName() addr : " + System.identityHashCode(student.getName()));
System.out.println("student.getNo() addr : " + System.identityHashCode(student.getNo()));
Integer num = student.getNo();
System.out.println("num addr :" + System.identityHashCode(num));
num += 130;
System.out.println("after num addr : " + System.identityHashCode(num));
System.out.println("after student.getNo() addr : " + System.identityHashCode(student.getNo()));
System.out.println("更改之后:" + student);
console:
更改之前:Student(no=128, name=Json, lesson=C++)
student.getName() addr : 939047783
sname addr :939047783
after sname addr : 1237514926
after student.getName() addr : 939047783
student.getNo() addr : 548246552
num addr :548246552
after num addr : 835648992
after student.getNo() addr : 548246552
更改之后:Student(no=128, name=Json, lesson=C++)
- 无论是String还是Integer,都是将地址赋值给变量,而原来的对象student.getXxx()都不会有影响,而hiObj地址并没有改变,本质上是对这个地址的数据的修改。无论它修改与否,这个地址一直都是被
hashMap的key“hiObj”引用的。
String对象和字面量的字符串经典对比
String s1 = "good";
String s2 = "good";
String s3 = new String("good");
String s4 = "go";
String s5 = "od";
String s6 = new String("go");
String s7 = new String("od");
String s8 = "go" + "od";
String s9 = new String("go" + "od");
System.out.println("s1==s2 : " + (s1 == s2));
System.out.println("s1==s3 : " + (s1 == s3));
System.out.println("s1==s4+s5 : " + (s1 == s4 + s5));
System.out.println("s1==s4+s6 : " + (s1 == s4 + s6));
System.out.println("s1==s6+s7 : " + (s1 == s6 + s7));
System.out.println("s1==s6+s5 : " + (s1 == s6 + s5));
System.out.println("s3==s6+s7 : " + (s3 == s6 + s7));
System.out.println("s1==s8 : " + (s1 == s8));
System.out.println("s3==s8 : " + (s3 == s8));
System.out.println("s1==s9 : " + (s1 == s9));
System.out.println("s3==s9 : " + (s3 == s9));
console:
s1==s2 : true
s1==s3 : false
s1==s4+s5 : false
s1==s4+s6 : false
s1==s6+s7 : false
s1==s6+s5 : false
s3==s6+s7 : false
s1==s8 : true
s3==s8 : false
s1==s9 : false
s3==s9 : false
- 指向常量池同一个字符串常量地址相同,而s4+s5和s8的区别在于,s8的两个字符串在编译期已经存在与常量池,拼接成"good",而常量池中已经存在"good",
所以将地址赋值给s8,而s4+s5是在运行期动态执行拼接的,会重新分配内存地址给拼接好的字符串。 - 其他和new相关的都会在堆中分配不同的内存地址
包装类Integer的经典对比
Student student = new Student().setNo(new Integer(128)).setName(new String("Json")).setLesson("C++");
Integer num = student.getNo();
System.out.println("1,num ==new Integer(128) : "+(num ==new Integer(128)));
System.out.println("2,128 ==new Integer(128) : "+(128 ==new Integer(128)));
System.out.println("3,num.equals(new Integer(128)) : "+num.equals(new Integer(128)));
System.out.println("4,new Integer(128).equals(128) : "+new Integer(128).equals(128));
Integer a=127;
Integer b=127;
Integer c = 0;
Integer d = new Integer(128);
Integer e = new Integer(128);
Integer f = new Integer(0);
Integer g=128;
Integer h=128;
System.out.println("a==b " + (a == b));
System.out.println("a==b+c " + (a == b + c));
System.out.println("a==d " + (a == d));
System.out.println("d==e " + (d == e));
System.out.println("d.equals(e) " + d.equals(e));
System.out.println("d==e+f " + (d == e + f));
System.out.println("127==e+f " + (127 == e + f));
System.out.println("g==h " + (g == h));
System.out.println("g.equals(h) " + g.equals(h));
console:
1,num ==new Integer(128) : false
2,128 ==new Integer(128) : true
3,num.equals(new Integer(128)) : true
4,new Integer(128).equals(128) : true
a==b true
a==b+c true
a==d false
d==e false
d.equals(e) true
d==e+f true
127==e+f false
g==h false
g.equals(h) true
- “==”比较地址,而1,2地址不同
- 3,4比较值,由于Integer重写的equals方法调用intValue方法,最终比较的还是int,所以建议比较Integer对象的时候使用equals方法。
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
- 127==e+f,Integer与int比较,会拆箱成int再做比较,遇到运算符也是先拆箱再计算。
- a==b,g==h,一个为true,一个为false的原因是IntegerCache,如果值的范围是[-128,127],那直接从缓存数组中取值,内存地址就是同一个,否则不同。
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
//valueOf(int i)方法
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
至此,若有纰漏,望各位不吝赐教