java String比較相等時必須使用equals的引伸

        java string比較時必須使用equals,這是一個定論,可是要了解這個規則確實須要很多知識。很多書上告訴你==只能比較地址,地址不同,那就是false,string存在着相同字面量不一樣地址的問題。知道的多一些的,還知道有常量池,可是什麼樣的string在常量池呢,本文就詳細的說一下各類狀況。java

string常量池

        你們都知道string能經過==返回true是由於常量池,他們的地址是相同的,在兩種狀況下咱們會用到,第一種是編譯階段,java在編譯成字節碼的同時,會把常量標誌出來,在編譯階段肯定的字符串會進入常量池;第二種狀況就是string的intern方法,這個方法會把調用的字符串放入常量池(jdk1.7之後中會直接獲取堆中string對象的引用,文章後面會具體說),而且返回常量池的地址,若是常量池有相同的字符串,則返回常量池的地址,再也不建立。app

舉例展現    

String a = "xxx";
String b = "xxx";

這種狀況a==b爲true,由於在編譯時已經肯定了xxx的字符串jvm

String a ="xxx1";
final int  value =1;
String b="xxx"+value;

這種狀況下a==b爲true,在編譯時final的值已經肯定,編譯器進行了宏替換。優化

String a="xxx1";
int value =1;
String b="xxx"+value;

這種狀況下a==b爲flase,在編譯是value做爲一個變量出現,沒法肯定value的值,因此b對象的內容不會進入常量池。ui

String a ="xxx1";
final int value=getNum();// getNum()是根據各類變量計算返回1
String b="xxx"+value;

這種狀況下a==b爲false,此處的final 修飾的value只是一個不可變的值,在編譯時期根本不知道數據是多少,此時編譯不會出現宏替換的狀況,b對象的內容也不會進入常量池。spa

String a= "xxx";
String b= new String("xxx");

這種狀況下a==b爲false,由於b是堆中新建的地址,a的地址在常量池,地址不一樣。調試

String a = "xxx";
String b=new String("xxx").intern();

這種狀況下a==b爲true,a對象的地址在常量池,新建的string雖然在堆,可是執行了intern方法,在運行時,獲取到了常量池xxx的地址,而且給b,此時a和b的地址是相同的。code

加號的解析

String x ="aa"+"bb";

上面狀況常量池有幾個String對象,猜想不少,這裏就不列舉了,最終只有一個對象進入常量池,就是aabb,至於aa,bb在編譯時已經作了優化,並不會放入常量池中。對象

int value =10;
String x ="xxx"+value;

x引用的對象在哪裏?堆仍是棧(上面已經驗證過常量池中沒有),實際是在堆中。blog

下面就是用Javap查看的執行過程(sunjdk),String x="xxx"+value;的過程被解析成爲Stringbuilder.append(「xxx」),Stringbuilder.append(value),Stringbuilder.toString(),三個步驟。

Stringbuilder.toString()就是新建了一個String對象。

jdk變更

    隨着jdk的變更,有一些代碼執行的結果也就不同了。1.7開始移動常量池進入堆中了。

String str = new String(new char[]{'a','b'});
	          str.intern();
	          String str1 ="ab";

        若是在你熟悉API和string常量池的解析後,認爲是str==str1爲flase的請往下看。

        在1.7以前這個代碼str和str1是不相等的,可是從1.7開始,這個str和str1地址相等了。在1.7以前,字符串常量池是在方法區中,new出來的對象在堆,調用Intern方法確定是在字符串常量池中新建了一個對象,可是jvm爲了不字符串常量池那邊oom,1.7開始把字符串常量池移入到了堆內存,在常量池沒有ab字符串的時候,str.intern()方法會讓常量池拿到堆裏對象的引用,並無新建一個對象,因此str1指向的對象就和str指向的對象相同了。這個能夠直接在調試的時候查看對象ID來檢測,或者使用hsdb來查看內存來驗證。下面推薦一篇解析了intern,native代碼的博客,幫助理解。http://brokendreams.iteye.com/blog/2260870

常見問題補充

1,java爲何不是第一次出現

String  sb = new StringBuilder("ja").append("va").toString();

    上面java是咱們第一次寫代碼出現,可是常量池中早已有這個關鍵字,因此Intern返回的地址是早已進入常量池中的地址。

2,產生對象個數問題

String  sb = new StringBuilder("hello").toString();

    此時產生了2個hello對象,一個在常量池,一個在內存。具體請查看 StringBuilder的構造過程。已經返回對象的過程,下面是stringbuilder構造時調用的一個方法

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

    代碼的最後作了一次char[]數據拷貝。把傳入的string轉化成char[]保持成成員變量。而後看tostring的過程,就是新建了一個string對象

public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

    這裏說的兩個Hello對象,一個是做爲StringBuilder的參數傳入的(常量池),一個是做爲返回值新建的(堆中)。

相關文章
相關標籤/搜索