Java:對象創建和初始化過程
1.Java中的數據類型
Java中有3個數據類型:基本數據類型(在Java中,boolean、byte、short、int、long、char、float、double這八種是基本數據類型)、引用類型和null類型。其中,引用類型包括類類型(含數組)、接口類型。
下列語句聲明了一些變量:
1
2
3
4
|
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中的變量聲明:
現在,你應該對下面的注釋一目了然。
1
2
3
4
|
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中的變量聲明,再看看下面的注釋。
1
2
3
|
A a; //聲明句柄a,但未初始化,所以里面的值為null。 B b1,b2,…,b10000; // 聲明句柄b1,…,b10000,但未初始化,所以里面的值為null。 String s; //聲明句柄s,但未初始化,所以里面的值為null。 |
4.句柄與引用的關系
1
2
|
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對象。(回想一下句柄與引用)
將上面的語句分為兩個步驟:
1
2
|
Vehicle veh1; veh1 = new Vehicle(); |
這樣寫,就比較清楚了,有兩個實體:一是對象引用變量,一是對象本身。在堆空間里創建的實體,與在棧空間里創建的實體不同。盡管它們也是確確實實存在的實體,但是似乎很難準確的“抓”住它。我們仔細研究一下第二句,找找剛創建的對象叫什么名字?有人說,它叫“Vehicle”。不對,“Vehicle”是類(對象的創建模板)的名字。一個Vehicle類可以據此創建出無數個對象,這些對象不可能全叫“Vehicle”。對象連名都沒有,沒法直接訪問它。我們只能通過對象引用來間接訪問對象。
6.2 Vehicle veh2;
1
|
veh2 = veh1; |
由于veh1和veh2只是對對象的引用,第二行所做的不過是把veh1的引用(地址)賦值給veh2,使得veh1和veh2同時指向唯一的一個Vehicle對象。
6.3 veh2 = new Vehicle();
則引用變量veh2改指向第二個對象。
從以上敘述再推演下去,我們可以獲得以下結論:①一個對象引用可以指向0個或1個對象;②一個對象可以有N個引用指向它。
Java:數據類型轉換
1.Java的簡單類型及其封裝器類
1.1Java簡單類型與封裝類
我們知道,Java語言是典型的支持面向對象的程序語言,但考慮到有些基本數據類型的結構簡單,占內存小且存取速度快等優點,Java依然提供了對這些非面向對象的簡單數據類型的支持。當然,Java在提供大量的其它類時,也提供了與簡單數據類型對應的封裝類,于是,Java中就有了諸如int和Integer(float和Float、double和Double……)的不同的數據類型。
Java語言的數據類型有兩大類:一類是簡單類型,也稱主要類型(Primitive),另一類是引用類型(Reference)。簡單類型變量中存儲的是具體的值,而引用類型的變量中存儲的是對象的引用。
Java決定了每種簡單類型的大小。這些大小并不隨著機器結構的變化而變化。這種大小的不可更改正是Java程序具有很強移植能力的原因之一。
下表列出了Java中定義的簡單類型、占用二進制位數及對應的封裝器類。
表 Java中的簡單類型
1.2為什么使用封裝類
以int和Integer為例來說,雖然從本質上它們都代表一個32位的整數,但它們卻是不同的數據類型。事實上,Java中直接使用的整數都為int(就int和Integer而言),只有當數據必須作為對象的身份出現時,才必須用int對應的封裝器Intege將整數值封裝成對象。
例如:為給java.util包中的Vector添加一個整數,就必須如下將該整數值封裝在一個Integer實例中:
1
2
3
|
Vector v= new Vector(); int k= 121 ; v.addElemt( new Integer(k)); |
另外,Integer作為int對應的封裝器類,提供了許多的方法,比如:Integer的構造方法、Integer向其它各種數值類型的轉換方法等等,而這些是int類型數據所沒有的。
2.Java中的常量
我們需要注意以下幾種類型的常量。
2.1十六進制整型常量
以十六進制表示時,需以0x或0X開頭,如0xff,0X9A。
2.2八進制整型常量
八進制必須以0開頭,如0123,034。
2.3長整型
長整型必須以L作結尾,如9L,342L。
2.4浮點數常量
由于小數常量的默認類型是double型,所以float類型的后面一定要加f(F)。同樣帶小數的變量默認為double類型。
float f;
1
|
f= 1 .3f; //必須聲明f。 |
2.5字符常量
字符型常量需用兩個單引號括起來(注意字符串常量是用兩個雙引號括起來)。Java中的字符占兩個字節。
一些常用的轉義字符。
①\r表示接受鍵盤輸入,相當于按下了回車鍵;
②\n表示換行;
③\t表示制表符,相當于Table鍵;
④\b表示退格鍵,相當于Back Space鍵;
⑤\'表示單引號;
⑥\''表示雙引號;
⑦\\表示一個斜杠\。
3.簡單數據類型之間的轉換
簡單類型數據間的轉換,有兩種方式:自動轉換和強制轉換,通常發生在表達式中或方法的參數傳遞時。
3.1自動轉換
具體地講,當一個較“小”數據與一個較“大”的數據一起運算時,系統將自動將“小”數據轉換成“大”數據,再進行運算。而在方法調用時,實際參數較“小”,而被調用的方法的形式參數數據又較“大”時(若有匹配的,當然會直接調用匹配的方法),系統也將自動將“小”數據轉換成“大”數據,再進行方法的調用,自然,對于多個同名的重載方法,會轉換成最“接近”的“大”數據并進行調用。
這些類型由“小”到“大”分別為 (byte,short,char)--int--long--float—double。這里我們所說的“大”與“小”,并不是指占用字節的多少,而是指表示值的范圍的大小。
請看下面的示例:
①下面的語句可以在Java中直接通過:
1
2
3
4
5
6
|
byte b; int i=b; long l=b; float f=b; double d=b; |
②如果低級類型為char型,向高級類型(整型)轉換時,會轉換為對應ASCII碼值,例如
1
2
3
|
char c= 'c' ; int i=c; System.out.println( "output:" +i); |
輸出:
1
|
output:99; |
③對于byte,short,char三種類型而言,他們是平級的,因此不能相互自動轉換,可以使用下述的強制類型轉換。
1
2
3
|
short i= 99 ; char c=( char )i; System.out.println( "output:" +c); |
輸出:
1
|
output:c; |
④對象多態中若有方法:
1
2
3
4
5
6
|
f( byte x){……}; f( short x) {……}; f( int x) {……}; f( long x) {……}; f( float x) {……}; f( double x) {……}; |
又有:char y='A';那么,語句f(y)會調用哪一個方法呢?答案是:f(int x) {……}方法,因為它的形參比實參“大”且是最“接近”的。
而對于方法:
1
2
|
f( float x) {……}; f( double x) {……}; |
又有:long y=123L;那么,語句f(y)調用的方法則是f(float x) {……}。
3.2強制轉換
將“大”數據轉換為“小”數據時,你可以使用強制類型轉換。即你必須采用下面這種語句格式:
1
|
int n=( int ) 3.14159 / 2 ; |
可以想象,這種轉換肯定可能會導致溢出或精度的下降。
3.3表達式的數據類型自動提升
關于類型的自動提升,注意下面的規則。
①所有的byte,short,char型的值將被提升為int型;
②如果有一個操作數是long型,計算結果是long型;
③如果有一個操作數是float型,計算結果是float型;
④如果有一個操作數是double型,計算結果是double型;
例,
1
2
3
|
byte b; b= 3 ; b=( byte )(b* 3 ); //必須聲明byte。 |
3.4包裝類過渡類型轉換
一般情況下,我們首先聲明一個變量,然后生成一個對應的包裝類,就可以利用包裝類的各種方法進行類型轉換了。例如:
①當希望把float型轉換為double型時:
1
2
3
|
float f1= 100 .00f; Float F1= new Float(f1); double d1=F1.doubleValue(); //F1.doubleValue()為Float類的返回double值型的方法 |
②當希望把double型轉換為int型時:
1
2
3
|
double d1= 100.00 ; Double D1= new Double(d1); int i1=D1.intValue(); |
簡單類型的變量轉換為相應的包裝類,可以利用包裝類的構造函數。即:Boolean(boolean value)、Character(char value)、Integer(int value)、Long(long value)、Float(float value)、Double(double value)
而在各個包裝類中,總有形為××Value()的方法,來得到其對應的簡單類型數據。利用這種方法,也可以實現不同數值型變量間的轉換,例如,對于一個雙精度實型類,intValue()可以得到其對應的整型變量,而doubleValue()可以得到其對應的雙精度實型變量。
4.字符串與其它類型間的轉換
4.1其它類型向字符串的轉換
①調用類的串轉換方法:X.toString();
②自動轉換:X+“”;
③使用String的方法:String.volueOf(X);
4.2字符串作為值,向其它類型的轉換
①先轉換成相應的封裝器實例,再調用對應的方法轉換成其它類型
例如,字符中“32.1”轉換double型的值的格式為:new Float(“32.1”).doubleValue()。也可以用:Double.valueOf(“32.1”).doubleValue()
②靜態parseXXX方法
1
2
3
4
5
6
7
|
String s = "1" ; byte b = Byte.parseByte( s ); short t = Short.parseShort( s ); int i = Integer.parseInt( s ); long l = Long.parseLong( s ); Float f = Float.parseFloat( s ); Double d = Double.parseDouble( s ); |
③Character的getNumericValue(char ch)方法
具體可查閱api。
5.Date類與其它數據類型的相互轉換
整型和Date類之間并不存在直接的對應關系,只是你可以使用int型為分別表示年、月、日、時、分、秒,這樣就在兩者之間建立了一個對應關系,在作這種轉換時,你可以使用Date類構造函數的三種形式:
①Date(int year, int month, int date):以int型表示年、月、日
②Date(int year, int month, int date, int hrs, int min):以int型表示年、月、日、時、分
③Date(int year, int month, int date, int hrs, int min, int sec):以int型表示年、月、日、時、分、秒
在長整型和Date類之間有一個很有趣的對應關系,就是將一個時間表示為距離格林尼治標準時間1970年1月1日0時0分0秒的毫秒數。對于這種對應關系,Date類也有其相應的構造函數:Date(long date)。
獲取Date類中的年、月、日、時、分、秒以及星期你可以使用Date類的getYear()、getMonth()、getDate()、getHours()、getMinutes()、getSeconds()、getDay()方法,你也可以將其理解為將Date類轉換成int。
而Date類的getTime()方法可以得到我們前面所說的一個時間對應的長整型數,與包裝類一樣,Date類也有一個toString()方法可以將其轉換為String類。
有時我們希望得到Date的特定格式,例如20020324,我們可以使用以下方法,首先在文件開始引入,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import java.text.SimpleDateFormat; import java.util.*; java.util.Date date = new java.util.Date(); //如果希望得到YYYYMMDD的格式 SimpleDateFormat sy1= new SimpleDateFormat( "yyyyMMDD" ); String dateFormat=sy1.format(date); //如果希望分開得到年,月,日 SimpleDateFormat sy= new SimpleDateFormat( "yyyy" ); SimpleDateFormat sm= new SimpleDateFormat( "MM" ); SimpleDateFormat sd= new SimpleDateFormat( "dd" ); String syear=sy.format(date); String smon=sm.format(date); String sday=sd.format(date); |