引用類型?常量池?包裝類緩存?舉個栗子

測試用的實體類

@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);
    }

至此,如有紕漏,望各位不吝賜教java

相關文章
相關標籤/搜索