java對象的創建與初始化

1.Java中的數據類型
    Java中有3個數據類型:基本數據類型(在Java中,boolean、byte、short、int、long、char、float、double這八種是基本數據類型)、引用類型和null類型。其中,引用類型包括類類型(含數組)、接口類型。
    下列語句聲明瞭一些變量:
int k ;
A a; //a是A數據類型的對象變量名。
B b1,b2,…,b10000;// 假定B是抽象類或接口。
String s; 

     注意:從數據類型與變量的角度看,基本數據類型變量k、類類型變量a和s、抽象類或接口類型變量b(1萬個),它們都是變量(標識符)。
 
2.關於句柄(handle)
    爲了區別引用類型的變量標識符和基本數據類型變量標識符,我們特別的使用Handle來稱呼引用類型的變量標識符。上面例子中b1至b10000、a、s都是Handle。Handle直觀的看就是手柄、把手,我們採用計算機界常用的中文翻譯“句柄”。
 
2.1【Windows編程中的】句柄的含義
    句柄是WONDOWS用來標識被應用程序所建立或使用的對象的唯一整數,WINDOWS使用各種各樣的句柄標識諸如應用程序實例,窗口,控制,位圖,GDI對象等等。WINDOWS句柄有點象C語言中的文件句柄。 
    從上面的定義中的我們可以看到,句柄是一個標識符,是拿來標識對象或者項目的,它就象我們的姓名一樣,每個人都會有一個,不同的人的姓名不一樣,但是,也可能有一個名字和你一樣的人。從數據類型上來看它只是一個16位的無符號整數。應用程序幾乎總是通過調用一個WINDOWS函數來獲得一個句柄,之後其他的WINDOWS函數就可以使用該句柄,以引用相應的對象。
    如果想更透徹一點地認識句柄,我可以告訴大家,句柄是一種指向指針的指針。我們知道,所謂指針是一種內存地址。應用程序啓動後,組成這個程序的各對象是駐留在內存的。如果簡單地理解,似乎我們只要獲知這個內存的首地址,那麼就可以隨時用這個地址訪問對象。但是,如果您真的這樣認爲,那麼您就大錯特錯了。我們知道,Windows是一個以虛擬內存爲基礎的操作系統。在這種系統環境下,Windows內存管理器經常在內存中來回移動對象,依此來滿足各種應用程序的內存需要。對象被移動意味着它的地址變化了。如果地址總是如此變化,我們該到哪裏去找該對象呢?
    爲了解決這個問題,Windows操作系統爲各應用程序騰出一些內存儲地址,用來專門登記各應用對象在內存中的地址變化,而這個地址(存儲單元的位置)本身是不變的。Windows內存管理器在移動對象在內存中的位置後,把對象新的地址告知這個句柄地址來保存。這樣我們只需記住這個句柄地址就可以間接地知道對象具體在內存中的哪個位置。這個地址是在對象裝載(Load)時由系統分配給的,當系統卸載時(Unload)又釋放給系統。
    句柄地址(穩定)→記載着對象在內存中的地址────→對象在內存中的地址(不穩定)→實際對象
 
2.2Java中句柄的意義
    對句柄以前的【Windows編程中的】含義有了深刻的認識,我們可以說Handle是一個我們學習Java時非常需要的術語。它的意義在於區別“對象本身”和對象變量(或者嚴格點:對象所屬的數據類型的變量標識符)。
 
2.3回到1中的變量聲明:
    現在,你應該對下面的註釋一目瞭然。
int k, j ;//k裏面存放的是一個整型數。
A a; //a裏面存放地址。
B b1,b2,…,b10000;// b1,…,b10000裏面存放地址。
String s; //s裏面存放地址。
 
3.關於引用(reference)
    什麼是“引用”? “the identifier you manipulate is actually a ‘reference’ to an object”。(Thinking in Java 2e )
    翻譯是:你操縱的標識符實際上是一個對象的“引用”。或者精確些,翻譯成:你操作的標識符實際上是指向一個對象的“引用”。顯然,原文中reference是一個有方向感的東西。
    回到Java中來,引用可以想象成對象的身份證號碼、對象的ID或者對象的手機號碼。當然,更多的說法是,引用是對象在內存中住的房間號碼。直觀的說,對象的引用是創建對象時的返回值!引用是new表達式的返回值。
    new A(); 這裏真正創建了一個對象,但我們沒有用句柄去持有(hold、拿着、保存)該引用。從微觀上看,new表達式完成了對象初始化的任務(三步曲,下文詳細分析),整體上看則返回一個引用。
    再次回到1中的變量聲明,再看看下面的註釋。
A a; //聲明句柄a,但未初始化,所以裏面的值爲null。
B b1,b2,…,b10000;// 聲明句柄b1,…,b10000,但未初始化,所以裏面的值爲null。
String s; //聲明句柄s,但未初始化,所以裏面的值爲null。
 
4.句柄與引用的關係
A a;//聲明句柄a,值爲null
a=new A();//句柄的初始化(句柄 = 引用;即把引用賦值給句柄)
    引用:new A()的值。引用可以簡單的看作對象佔據內存空間的地址;通過對象的引用,就可以方便的與其他對象區別開來,引用就是對象獨特的身份標識。
完成句柄的初始化後,就可以用句柄遙控對象了。
    當然,這只是從一方面解釋對象的創建和初始化,理解了句柄和引用的關係後,下面分析對象初始化的整個過程。先做以下準備工作,說說棧與堆。
 
5.java中棧(stack)與堆(heap)
    在java中內存分爲“棧”和“堆”這兩種(Stack and Heap).基本數據類型存儲在“棧”中,對象引用類型實際存儲在“堆”中,在棧中只是保留了引用內存的地址值。
    順便說說“==”與“equals()方法”,以幫助理解兩者(Stack and Heap)的概念。
    在Java中利用"=="比較變量時候,系統使用變量在stack(棧)中所存的值來作爲對比的依據,基本數據類型在stack中所存的值就是其內容值,而引用類型在stack中所存放的值是本身所指向Heap中對象的地址值。 Java.lang包中的Object類有public boolean equals (Object obj)方法。它比較兩個對象是否相等。僅當被比較的兩個引用指向同一對象時(句柄相等),對象的equals()方法返回true。(至於String類的equals()方法,它重寫(override)equals()方法,不在本文討論之列。)
 
6.對象的創建和初始化過程
    在java中對象就是類的實例。在一般情況下,當把一個類實例化時,此類的所有成員,包括變量和方法,都被複制到屬於此數據類型的一個新的實例中去。分析以下兩段代碼。
 
6.1  Vehicle veh1 = new Vehicle();
    上面的語句做了如下的事情:
①右邊的“new Vehicle”,是以Vehicle類爲模板,在堆空間裏創建一個Vehicle類對象(也簡稱爲Vehicle對象)。
②末尾的()意味着,在對象創建後,立即調用Vehicle類的構造函數,對剛生成的對象進行初始化。構造函數是肯定有的。如果沒創建,Java會補上一個默認的構造函數。
③左邊的“Vehicle veh1”創建了一個Vehicle類引用變量。
④“=”操作符使對象引用指向剛創建的那個Vehicle對象。(回想一下句柄與引用)
    將上面的語句分爲兩個步驟:
Vehicle veh1;
veh1 = new Vehicle();

    這樣寫,就比較清楚了,有兩個實體:一是對象引用變量,一是對象本身。在堆空間裏創建的實體,與在棧空間裏創建的實體不同。儘管它們也是確確實實存在的實體,但是似乎很難準確的“抓”住它。我們仔細研究一下第二句,找找剛創建的對象叫什麼名字?有人說,它叫“Vehicle”。不對,“Vehicle”是類(對象的創建模板)的名字。一個Vehicle類可以據此創建出無數個對象,這些對象不可能全叫“Vehicle”。對象連名都沒有,沒法直接訪問它。我們只能通過對象引用來間接訪問對象。
 
6.2  Vehicle veh2;
veh2 = veh1;
    由於veh1和veh2只是對對象的引用,第二行所做的不過是把veh1的引用(地址)賦值給veh2,使得veh1和veh2同時指向唯一的一個Vehicle對象。
 
6.3  veh2 = new Vehicle();
    則引用變量veh2改指向第二個對象。
    從以上敘述再推演下去,我們可以獲得以下結論:①一個對象引用可以指向0個或1個對象;②一個對象可以有N個引用指向它。 


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1903130

 

1.Java中的基本數據類型
    Java中有2種基本數據類型:基本數據類型(在Java中,boolean、byte、short、int、long、char、float、double這八種是基本數據類型)、引用類型。其中,引用類型包括類類型(含數組)、接口類型。
2.java中棧(stack)與堆(heap)
    在java中內存分爲“棧”和“堆”這兩種(Stack and Heap).基本數據類型存儲在“棧”中,對象引用類型實際存儲在“堆”中,在棧中只是保留了引用內存的地址值。
3.基本數據類型在棧中的存儲
    基本類型的定義是通過諸如int a = 3; long b = 255L;的形式來定義的,稱爲自動變量。值得注意的是,自動變量存的是字面值,不是類的實例,即不是類的引用,這裏並沒有類的存在。如int a = 3; 這裏的a是一個指向int 類型的引用,指向3這個字面值。這些字面值的數據,由於大小可知,生存期可知(這些字面值固定定義在某個程序塊裏面,程序塊退出後,字段值就消失了),出於追求速度的原因,就存在於棧中。
    另外,棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義
int a = 3; 
int b = 3;

    編譯器先處理int a = 3;首先它會在棧中創建一個變量爲a的引用,然後查找有沒有字面值爲3的地址,沒找到,就開闢一個存放3這個字面值的地址, 然後將a指向3的地址。接着處理int b = 3;在創建完b的引用變量後,由於在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a 與b同時均指向3的情況。
    特別注意的是,這種字面值的引用與類對象的引用不同。假定兩個類對象的引用同時指向一個對象,如果一個對象引用變量修改了這個對象的內部狀態,那麼另一個對象引用變量也即刻反映出這個變化。相反,通過字面值的引用來修改其值,不會導致另一個指向此字面值的引用的值也跟着改變的情況。如上例,我們定義完a與b的值後,再令a=4;那麼,b不會等於4,還是等於3。在編譯器內部,遇到a=4;時,它就會重新搜索棧中是否有 4的字面值,如果沒有,重新開闢地址存放4的值;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
4.關於String str = "abc"的內部工作
    Java內部將此語句轉化爲以下幾個步驟:
①先定義一個名爲str的對String類的對象引用變量:String str;
②在棧中查找有沒有存放值爲"abc"的地址,如果沒有,則開闢一個存放字面值爲"abc"的地址,接着創建一個新的String類的對象o,並將o 的字符串值指向這個地址,而且在棧中這個地址旁邊記下這個引用的對象o。如果已經有了值爲"abc"的地址,則查找對象o,並返回o的地址。
③將str指向對象o的地址。
    值得注意的是,一般String類中字符串值都是直接存值的。但像String str = "abc";這種場合下,其字符串值卻是保存了一個指向存在棧中數據的引用!
    爲了更好地說明這個問題,我們可以通過以下的幾個代碼進行驗證。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2);  //true
    注意,我們這裏並不用str1.equals(str2);的方式,因爲這將比較兩個字符串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了同一個對象時才返回真值。而我們在這裏要看的是,str1與str2是否都指向了同一個對象。
    再看以下代碼:
String str1 = "abc";
String str2 = "a"+"bc";
System.out.println(str1==str2);  //true
    由上面兩段代碼結果說明,JVM創建了兩個引用str1和str2,但只創建了一個對象,而且兩個引用都指向了這個對象。
    我們再來更進一步,將以上代碼改成:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
System.out.println(str1 + "," + str2);  //bcd, abc
System.out.println(str1==str2);  //false
    這就是說,賦值的變化導致了類對象引用的變化,str1指向了另外一個新對象!而str2仍舊指向原來的對象。上例中,當我們將str1的值改爲"bcd"時,JVM發現在棧中沒有存放該值的地址,便開闢了這個地址,並創建了一個新的對象,其字符串的值指向這個地址。
    事實上,String類被設計成爲不可改變(immutable)的類。如果你要改變其值,可以,但JVM在運行時根據新值悄悄創建了一個新對象,然後將這個對象的地址返回給原來類的引用。
    再修改原來代碼:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
String str3 = str1;
System.out.println(str3);  //bcd
String str4 = "bcd";
System.out.println(str1 == str4);  //true
   str3 這個對象的引用直接指向str1所指向的對象(注意,str3並沒有創建新對象)。當str1改完其值後,再創建一個String的引用str4,並指向因str1修改值而創建的新的對象。可以發現,這回str4也沒有創建新的對象,從而再次實現棧中數據的共享。
    我們再接着看以下的代碼。
String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2);  //false
    創建了兩個引用。創建了兩個對象。兩個引用分別指向不同的兩個對象。
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2);  //false
 
    創建了兩個引用。創建了兩個對象。兩個引用分別指向不同的兩個對象。
    以上兩段代碼說明,只要是用new()來新建對象的,都會在堆中創建,而且其字符串是單獨存值的,即使與棧中的數據相同,也不會與棧中的數據共享。
    這裏作如下總結(對後文引入intern()方法有幫助):
 
String str1 = "abc";                     //共享內容值
String str2 = new String("abc");//不共享內容值
 
    我們從另一個角度來分析上面兩句語句,從而引入常量池的概念。常量池(constant pool)指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。它包括了關於類、方法、接口等中的常量,也包括字符串常量。
 
String str1 = "abc";//是字符串常量,它在編譯期被確定。
String str2 =new String("abc");
//不是字符串常量,不在編譯期確定。
//new String()創建的字符串不放入常量池中。
 
5.總結Java中處理字符串的機制
    由上面這些例子可發現JVM處理字符串的機制。Java虛擬機會維護一個內部的滯留字符串對象的列表(唯一字符串的池)來避免在堆內存中產生重複的String對象。當JVM從class文件里加載字符串字面量並執行的時候,它會先檢查一下當前的字符串是否已經存在於滯留字符串列表,如果已經存在,那就不會再創建一個新的String對象而是將引用指向已經存在的String對象,JVM會在內部爲字符串字面量作這種檢查,但並不會爲通過new關鍵字創建的String對象作這種檢查。當然你可以明確地使用String.intern()方法強制JVM爲通過 new關鍵字創建的String對象作這樣的檢查。這樣可以強制JVM檢查內部列表而使用已有的String對象。
    所以結論是,JVM會內在地爲字符串字面量維護一些唯一的String對象,程序員不需要爲字符串字面量而發愁,但是可能會被一些通過 new關鍵字創建的String對象而困擾,不過他們可以使用intern()方法來避免在堆內存上創建重複的String對象來改善Java的運行性能。
 
6.Java中處理字符串的相關方法
 
6.1 Java.lang.Object對象的equals()源代碼
Public boolean equals(Object obj){ 
  Return (this= =obj); 
}
    顯然,當兩個變量指向同一個對象時,equals()方法返回true。
 
6.2 String.equals()的代碼
public boolean equals(Object anObject) {
           if (this == anObject) {
               return true;
           }
           if (anObject instanceof String) {
               String anotherString = (String)anObject;
               int n = count;
               if (n == anotherString.count) {
                      char v1[] = value;
                      char v2[] = anotherString.value;
                      int i = offset;
                      int j = anotherString.offset;
                      while (n-- != 0) {
                          if (v1[i++] != v2[j++]) 
                              return false;
                      }
               return true;
               }
           }
           return false;
    }
    由此,可發現String.equals() 方法比較二者的內容,是一個個的比較的。它不同與java.lang.Object的equals()方法,它僅僅比較兩個對象的引用。
6.3 String的intern()方法
    存在於.class文件中的常量池,在運行期被JVM裝載,並且可以擴充。String的intern()方法就是擴充常量池的一個方法;當一個String實例str調用intern()方法時,Java查找常量池中是否有相同Unicode的字符串常量,如果有,則返回其的引用,如果沒有,則在常量池中增加一個Unicode等於str的字符串並返回它的引用;看下面代碼:
 
  String s0= “abc”;
  String s1=new String(”abc”); 
  String s2=new String(“abc”);
 
  System.out.println( s0==s1 );
 
  s1.intern(); 
  s2=s2.intern();  //把常量池中“abc”的引用賦給s2
 
System.out.println( s0==s1); 
System.out.println( s0==s1.intern() ); 
System.out.println( s0==s2 );
 
    結果爲: 
false       
false       //雖然執行了s1.intern(),但它的返回值沒有賦給s1 
true       //說明s1.intern()返回的是常量池中”abc”的引用 
true       ////說明s2.intern()返回的是常量池中”abc”的引用
 
    回到前文引出常量池定義的部分:
String str1 = "abc";//是字符串常量,它在編譯期被確定。
String str2 =new String("abc");
//不是字符串常量,不在編譯期確定。
//new String()創建的字符串不放入常量池中。
    看下面代碼:
String s1=new String("abc"); 
String s2=s1.intern();
 
System.out.println( s1==s1.intern() ); 
System.out.println( s1+"  "+s2 ); 
System.out.println( s2==s1.intern() );
 
    結果: 
false    //說明原來的“abc”仍然存在
abc  abc
true    // s2現在爲常量池中“abc”的地址,所以有s2==s1.intern()爲true
 
    當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(該對象由 equals(Object) 方法確定),則返回池中的字符串。否則,將此 String 對象添加到池中,並且返回此 String 對象的引用。這是Java api文檔中關於intern 方法的定義。
    回到上面的代碼示例。在這個類中一開始,我們沒有聲名一個”abc”常量,所以常量池中一開始是沒有”abc”。當我們調用s1.intern()後就在常量池中新添加了一個”abc”常量,原來的不在常量池中的”abc”,即s1(表現爲存儲地址)仍然存在,所以s1==s1.intern()返回flase。
發佈了1 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章