第三章 賦值java
3.1 棧和堆——快速回顧數組
這是一篇講解Java堆與棧的文章:http://neilduan.iteye.com/blog/426830 數據結構
考試目標裏並無Java堆棧的內容,可是瞭解這些內容對咱們理解Java工做的機制有很大的幫助。 函數
- 實例變量和對象駐留在堆(heap)上。
- 局部變量駐留在棧(stack)上。
3.2 字面值、賦值和變量工具
考試目標1.3 編寫代碼,將基本類型、數組、枚舉和對象做爲靜態變量、實例變量和局部變量聲明、初始化並使用。此外,使用合法的標識符爲變量命名。測試
考試目標7.6 編寫代碼,正確應用恰當的運算符,包括賦值運算符(限於=、+=、-=)。ui
3.2.1 全部基本類型字面值this
基本類型字面值就是咱們在給基本類型賦值時「=」後面的「值」。spa
整形字面值:Java有3種表示整數的方法:十進制、八進制、十六進制。線程
- 十進制字面值:還用說嗎?
- 八進制字面值:「0」前綴,如:06,014,077
- 十六進制字面值:「0x」或「0X」前綴,如:0x001F,0X23Df(不區分大小寫)
- 字面值默認被定義爲int型,如加後綴「L」或「l」,則爲long型。
浮點字面值:默認爲double類型(64位),能夠不加「D」後綴。在數字後加「F」或「f」後綴,標識爲float型(32位)。
布爾字面值:Java的boolean字面值只能爲true或false,不能爲數字。
字符字面值:字符字面值有如下集中表示方式:
- 單引號內的單個字符。如:'a','@'
- 單引號內的Unicode值。如:'\u004E'
- 字符其實是一個16位無符號整數(小於等於65535),如:0x892,982。
- 用轉義字符表示不能做爲字面值鍵入的字符。如:'\"','\n'
字符串字面值:String對象值的源代碼表示。它並不是基本類型。如:String str = "Hello Java";
3.2.2 賦值運算符
基本變量賦值:
咱們知道每種字面值都會有默認的類型,在賦值時經常要注意聲明的類型是否跟賦的字面值類型匹配。
如 byte = 27,這種狀況能夠不強制轉換,可是下面的狀況是必須強制轉換的:
byte a = 3; byte b = 3; byte c = (byte)(a+b); //必須強制轉換
基本類型的強制轉換:
隱式的強制轉換會自動實現,好比short轉int,int轉long。
而當大轉小、浮點轉整型的時候,就須要顯式的強制轉換:
- 將浮點數強制轉換整數類型時,小時點後面的全部位都將丟失。
- 將長類型強制轉換短類型時,若是字面值超太短類型的範圍,會階段超出的高位。
浮點數賦值:默認爲double,如爲float,須要強制轉換或在字面值後加「F」後綴。
賦予變量一個過大的字面值:同長類型轉換短類型時的結果。
將一個基本變量賦予另外一個基本變量:則它們有徹底相等的副本,可是並不表示他們共享同一個副本。
引用變量賦值:
Button b = new Button(); 作了什麼?
- 創建一個名爲b的Button類型的引用變量
- 在堆上建立一個新的Button對象
- 將新建立的Button對象賦予引用變量b
變量做用域:
- 靜態變量具備最長的做用域。它是在加載類時建立的,而且只要類在Java虛擬機中保持加載狀態,它們就會一直存在。
- 實例變量的存在時間次之。它是在建立新實例時建立的,而且會存在到實例被刪除時爲止。
- 局部變量再次之。只要方法保持在棧上,它就會存在下去。可是,正如咱們很快將看到的,局部變量能夠存在下去,還能夠「超出做用域」。
- 僅當代碼塊在執行時,塊變量纔會存在。
class Layout { // 類 static int s = 343; // s是靜態變量 int x; // x是實例變量 { x = 7; int x2 = 5; //x2是初始塊變量,屬於局部變量 } Layout() { x += 8; int x3 = 6; //x3是構造函數變量,屬於局部變量 } void doStuff() { int y = 0; //y是局部變量 for (int z = 0; z < 4; z++) { //z是塊變量 y += z + x; } } }
做用域錯誤最多見的緣由是:試圖訪問一個不在做用域中的變量。下面是3個典型的例子:
//錯誤一:試圖從靜態上下文中訪問一個實例變量。 class ScopeErrors{ int x = 5 ; public static void main(String[] args){ x++; //編譯錯誤,x是一個實例變量 } } //錯誤二:試圖從嵌套方法訪問局部變量。 class ScopeErrors2{ public static void main(String[] args){ ScopeErrors2 s = new ScopeErrors2(); s.go(); } void go(){ int y = 5; go2(); y++; } void go2(){ y++; // 編譯錯誤,y是go()的局部變量 } //錯誤三:在代碼塊完成後試圖使用塊變量 void go3(){ for(int z = 0;z<5;z++){ boolean test = false; if(z == 3){ test = true; break; } } System.out.print(test); //編譯錯誤,test的生命週期已經結束了。 } }
3.2.3 使用未初始化或未賦值的變量或數組元素
基本類型和對象類型實例變量:
每次建立一個新的實例時,實例變量都會被初始化爲一個默認值,但在對象的超類構造函數完成以後給它賦予一個顯式值。
基本類型和對象類型的默認值表
變量類型 | 默認值 |
對象引用 | null |
byte,short,int,long | 0 |
float,double | 0.0 |
boolean | false |
char | '\u0000' |
數組實例變量若是未初始化變量,則按照上表給它的每一個項賦一個相應的默認值。
3.2.4 局部(棧、自動)基本變量和對象變量
局部基本變量
局部變量老是必須在使用它們以前初始化。Java不會爲局部變量賦予默認值,必須顯式初始化。
局部對象引用
同上,必須顯式的賦值爲null。
局部數組
必須顯式地初始化它,可是在構造數組對象時,其全部元素都會被賦予默認值。
將一個引用變量賦予另外一個引用變量
兩個引用將引用同一個實例,當對一個進行修改時,另外一個也變化。這與「將一個基本變量賦予另外一個基本變量」是不一樣的。
可是String類型除外。當使用String引用變量修改字符串時,會發生以下事情:
- 建立一個新字符串(或者在String池中發現一個匹配的String),並保持原來的String對象不變。
- 而後,將用於修改String的引用(或者經過修改原來的副本,創建一個新的String)賦予全新的String對象。
3.3 向方法傳遞變量
考試目標7.3 當將對象引用和基本值傳入方法中,並執行賦值或關於參數的其餘修改操做時,判斷對對象引用和基本值的影響。
3.3.1 傳遞對象引用變量
忍不了了。。理論的東西就是我以爲沒什麼好說的,但是寫書的卻能寫一大堆,你看完了還以爲確實是這樣,可是仍是說不出那麼一大堆來。
好了,我認可我暈菜了。。總之,傳遞對象引用變量的實際意思是:告訴引用處的兄弟,個人對象是從哪來的(告訴他這個對象在內存中的地址)。並非說我把本身的對象傳遞給了他,或複製了一個給他。
3.3.2 Java使用按值傳遞語法嗎
class Test { private String i ; public String getI() { return i; } public void setI(String i) { this.i = i; } }
class Test2{ public static void main(String[] args){ int j = 111; Test t1 = new Test(); t1.setI("Jack"); System.out.println("1="+t1.getI()); System.out.println("1.j="+j); Test2 t2 = new Test2(); t2.doStuff(j,t1); System.out.println("2="+t1.getI()); System.out.println("2.j="+j); } private void doStuff(int j,Test t){ j = 222; t.setI("Mike"); } }
運行的結果是:
1=Jack 1.j=111 2=Mike 2.j=111
咱們調用了Test2的doStuff()方法,向它傳入了兩個參數,一個基本類型,一個引用類型。
可見基本類型是按值傳遞的,傳給方法的是一個副本;
而引用類型是按引用傳遞的,傳給方法的是一個對象的引用地址。 調用與被調用處使用的是同一個地址的對象。
3.3.3 傳遞基本變量
同上,基本變量按值傳遞,傳遞該變量內的一個位副本。
變量的隱藏:
前面講static的時候,提到了重定義的概念。這實際上是一種隱藏效果,使它看起來就好像在使用被隱藏的變量,可是其實是使用隱藏變量。
常見的實現隱藏的方法:
- 直接聲明一個相同名稱的局部變量,在方法體內
- 做爲變元的一部分聲明一個相同名稱的局部變量,在變元內
3.4 數組聲明、構建和初始化
考試目標1.3 編寫代碼,將基本類型、數組、枚舉和對象做爲靜態變量、實例變量和局部變量聲明、初始化並使用。此外,使用合法的標識符爲變量命名。
數組是Java中的對象,它存儲多個相同類型的變量。
數組可以保存基本類型或對象引用,可是數組自己老是堆中的對象,即便數組被聲明爲用以保存基本類型的元素也是如此。
3.4.1 聲明數組
數組是經過說明它將要保存的元素類型來聲明的,元素類型能夠是對象或基本類型,類型後面的方括號能夠位於標識符的左邊或右邊。在聲明中不要包含長度。
int[] key; int key[]; //最好不要這樣聲明 Thread[][] threads; Thread[] threads[]; //最好不要這樣聲明
3.4.2 構建數組
構建數組意味着在堆(全部對象都存在於其中)上建立數組對象——即在數組類型上執行一次new操做。而且要指定數組大小。
構建一維數組:
int[] test; //聲明數組 test = new int[4]; //構建數組
構建多維數組:
int [] [] myArray = new int [3] []; //只聲明瞭一維的大小,這是容許的。 //換句話說咱們告訴JVM,myArray是由3個int[]組成的,這就能夠經過編譯了。
3.4.3 初始化數組
初始化數組意味着將內容放入數組中。
int[][] scores = new int[3][]; scores[0] = new int[4]; scores[1] = new int[6]; scores[2] = new int[1];
在循環中初始化元素
Dog[] myDogs = new Dog[6]; for(int x=0;x<myDogs.length;x++){ myDogs[x] = new Dog(); }
在一行內聲明、構建並初始化數組
int[] dots = {5,6,7,8}; Dog[] myDogs = {new Dog("Clover"), new Dog("Aiko")}; int[][] scores = {{1,3,4},{4,3,1,3},{3}};
構建和初始化匿名數組
int[] array = new int[] {3,4,5}; //匿名數組初始化時,千萬不要指定大小,大小由{}中的元素數來決定。
合法的數組元素賦值:
前面說到在聲明數組的時候只能有一種類型,但其實只要能順利向上轉換的均可以初始化到數組中。好比
//基本數組 int[] array = new int[5]; byte b = 4; char c = 'c'; short s = 7; array[0] = b; array[1] = c; array[2] = s; //對象引用數組 class Car{} class Ferrari extends Car{} Car[] myCars = {new Car(),new Ferrari()};
一維數組的數組引用賦值
上面說到(合法的數組元素賦值)咱們能夠將符合數組聲明的子類型值初始化給該數組,可是一旦這個數組的類型已經聲明瞭,並不能將他引用賦值給其餘的非自身類型的數組。
多維數組的數組引用賦值
維數要相等。
3.4.4 初始化塊
靜態初始化塊在類聲明時運行,實例初始化塊在類實例化時運行。
class Test{ static int x; int y; static {x = 7;} //static init block {y=8;} //instance init block }
- 初始化塊的執行次序遵循其出現的次序。
- 首次加載類時,會運行一次靜態初始化塊。
- 每當建立一個類實例時,都會運行實例初始化塊。
- 實例初始化在構造函數的super()調用以後運行。
3.5 使用包裝器類和裝箱
考試目標3.1 編寫代碼,使用基本包裝器類(如Boolean、Character、Double、Integer等) ,和/或自動裝箱以及拆箱。討論String、StringBuilder 以及 StringBuffer 類之間的區別。
JavaAPI中的包裝器類有兩個主要目的:
- 提供一種機制,將基本值「包裝」到對象中,從而使基本值可以包含在爲對象而保留的操做中。
- 爲基本值提供分類功能。
3.5.1 包裝器類概述
基本類型 | 包裝器類 | 構造函數變元 |
boolean | Boolean | boolean或String |
byte | Byte | byte或String |
char | Character | char |
double | Double | double或String |
float | Float | float、double或String |
int | Integer | int或String |
long | Long | long或String |
short | Short | short或String |
3.5.2 建立包裝器對象
包裝器構造函數
除Character以外,全部包裝器類都提供兩個構造函數:一個以要構建的基本類型做爲變元,另外一個以要構建類型的String表示做爲變元。如:
Integer i1 = new Integer(42); Integer i2 = new Integer("42");
valueOf()方法
Integer i1 = Integer.valueOf(42); Integer i2 = Integer.valueOf("42"); Integer i3 = Integer.valueOf("101011",2); //2進制
3.5.3 使用包裝器轉換實用工具
xxxValue()方法:將包裝器轉換爲基本類型
當須要將被包裝的數值轉換爲基本類型時,可以使用幾個xxxValue()方法之一。如:
Integer i = new Integer(42); byte b = i.byteValue();
parseXxx():將String轉換爲基本類型。
和valueOf():將String轉換爲包裝器。
double d1 = Double.parseDouble("3.1415"); Double d2 = Double.valueOf("3.1415");
toString()方法:方法返回String,其值爲包裝在對象內的基本類型值。
toXxxString()方法(二進制、十六進制、八進制)
Integer和Long包裝器類都容許將以10爲基數的數值轉換爲其餘基數。
String s = Integer.toHexString(254); String s = Long.toOctalString(254);
3.5.4 自動裝箱
Java5開始的新特性:裝箱,拆箱。在基本類型和包裝器之間使用時,再也不須要手動的調用轉換的方法,會自動轉換。
裝箱、==和equals()方法
咱們知道對於對象類型,若是其引用的地址相同,換句話說它們引用的是同一個對象,咱們能夠說「A==B爲true」;
若是它們的值相等,或者說「在乎義上是等價的」,咱們能夠說「A.equals(B)爲true」。
可是,爲了節省內存,對於下列包裝器對象的兩個實例(經過裝箱建立),當它們的基本值相同時,它們老是「==」關係:
- Boolean
- Byte
- 從 \u0000 到 \u007f 的字符(7f是十進制的127)
- -128~127的 Short 和 Integer
裝箱能用在什麼地方:只要可以正常使用基本變量或包裝對象,裝箱和拆箱都適用。
3.6 重載
考試目標1.5 給定一個代碼示例,判斷一個方法是否正確地重寫或重載了另外一個方法,並判斷該方法的合法返回值(包括協變式返回值)。
考試目標5.4 給定一個場景,編寫代碼,聲明和/或調用重寫方法或重載方法。編寫代碼,聲明和/或調用超類、重寫構造函數或重載構造函數。
重載帶來的難題——方法匹配
可能致使重載有點難於處理的3個因素:
- 加寬。如當變元爲float類型,而方法沒有float爲變元的,可是有double的,那麼將調用double類型的,這就是加寬。可是注意,不能變窄,若是沒有匹配類型則沒法經過編譯。
- 自動裝箱。
- var-arg。
下例用來體會加寬:
public class EasyOver { static void go(int x){System.out.print("int ");} static void go(long x){System.out.print("long ");} static void go(double x){System.out.print("double ");} public static void main(String[] args){ byte b = 5; short s = 5; long l = 5; float f = 5.0f; EasyOver.go(b); EasyOver.go(s); EasyOver.go(l); EasyOver.go(f); } }//結果是int int long double
帶有裝箱和var-arg的重載
public class AddBoxing {
static void go(Integer x){System.out.println("Integer");}
//static void go(long x){System.out.println("long");}
public static void main(String[] args){
int i = 5;
go(i);
}
}
//運行結果:long
//若是註釋掉go(long x)方法,運行結果:Integer
//可見 加寬優先於裝箱
public class AddVarargs {
static void go(int x , int y){System.out.println("int,int");}
static void go(int... x){System.out.println("int...");}
public static void main(String[] args){
int i = 5;
go(i,i);
}
}
//運行結果:int,int
//若是沒有go(int x,int y)方法,則運行結果爲int...
//可見 加寬優先於var-arg
public class BoxOrVararg {
static void go(Byte x,Byte y){System.out.println("Byte,Byte");}
static void go(byte... x){System.out.println("byte...");}
public static void main(String[] args){
byte b = 5;
go(b,b);
}
}
//運行結果:Byte,Byte
//可見 裝箱優先於var-arg
JVM選擇重載方法的優先順序是:加寬、裝箱、var-arg。
加寬引用變量
對於對象,也就是引用變量,引用加寬依賴於繼承,換句話說,依賴於IS-A測試。
因爲這種依賴,加寬IS-A關係的引用變量是合法的;可是也因爲IS-A依賴,從一個包裝器類加寬到另外一個包裝器類是非法的。包裝器類之間是平等的。如AddBoxing中,如「i」是short類型是沒法經過編譯的。
使用加寬、裝箱和var-arg的重載方法的幾條規則:
- 基本類型的加寬使用可能的「最小」方法變元。
- 當分別使用時,裝箱與var-arg都與重載兼容。
- 不能從一種包裝器類型加寬到另外一種包裝器類型(IS-A測試會失敗)。
- 不能(JVM自動)先加寬,後裝箱(int不能變成Long)。
- 能夠先裝箱,後加寬(int能夠經過Integer變成Object)。
- 能夠組合使用var-arg與加寬或裝箱。
3.7 垃圾收集
考試目標7.4 給定一個代碼示例,辨別對象從哪一個時刻開始複合垃圾收集條件,並判斷垃圾收集系統保證什麼、不保證什麼。理解 Object finalize()方法的行爲。
3.7.1 內存管理和垃圾收集概述
在C或C++等不提供自動垃圾收集的語言中,手工清空或刪除集合數據結構時,邏輯上的一點點缺陷可能會致使少許的內存被錯誤地回收或丟失。這種少許的內存丟失稱爲內存泄漏。通過N次的迭代以後,它們可能會致使足夠的內存變得不可訪問,是程序最終崩潰。
Java的垃圾收集器爲內存管理提供了一種自動解決方案。它能使你從必須爲應用程序添加全部內存管理邏輯的任務中解脫出來。缺點是不能徹底控制它何時執行與不執行。
3.7.2 Java垃圾收集器概述(Garbage Collection)
什麼時候運行垃圾收集器?
垃圾收集器受JVM控制,JVM決定何時運行垃圾收集器。
在任何狀況下都沒法保證JVM會答應你的請求,它是自動管理的。
如何運行垃圾收集器?
對象在什麼時候開始符合垃圾收集條件?
當沒有線程可以訪問對象時,該對象就是適合進行垃圾收集的。
3.7.3 編寫代碼,顯式地使對象複合垃圾收集條件
一、空引用
將對象賦值爲「null」,GC就會處理它。
二、爲引用變量從新賦值
經過設置引用變量引用另外一個對象來解除引用變量與對象間的引用關係。
三、隔離引用
隔離島的例子:
public class Island { Island i; public static void main(String[] args){ Island i2 = new Island(); Island i3 = new Island(); Island i4 = new Island(); i2.i = i3; i3.i = i4; i4.i = i2; i2 = null; i3 = null; i4 = null; } }
看上面的代碼,3個Island對象都擁有實例變量,它們相互引用,可是它們指向外界的鏈接已經被設置爲null。這3個對象都複合垃圾收集條件。
四、強制執行垃圾收集
實際上,只能建議由JVM執行垃圾收集,根本不能保證JVM從內存中實際刪除全部不使用的對象。
「請求」垃圾收集的最簡單方法:System.gc();(查看Runtime類 Runtime.gc())
五、垃圾收集前進行清理——finalize()方法
建議通常狀況下根本不要重寫finalize()方法。
- 對於任何給定的對象,finalize()方法最多隻會被垃圾收集器調用一次。
- 調用finalize()方法實際上可以致使對象免於被刪除。