引用类型?常量池?包装类缓存?举个栗子

测试用的实体类

@Data
@Accessors(chain = true)
class Student{
    private Integer no;
    private String name;
    private String lesson;
}

将一个map中的值取出来赋值给变量,此变量更改,这个map也会更改

        //存放对象的map
        Map hashMap = new HashMap<>();
        //新建一个studentMap存信息
        Map 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 hiObj = (Map) 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);
    }

至此,若有纰漏,望各位不吝赐教

你可能感兴趣的:(引用类型?常量池?包装类缓存?举个栗子)