thinking in java 筆記之控制程序流程

寫在前面:Hubert建議我看看《think in Java》並寫博客記錄一下筆記,於是有了此文,之後會持續更新。文中許多內容是直接引用書中的,因爲大家都知道,本文介紹的是Java基礎,就是這麼多這麼繁瑣的東西。希望大家耐心看微笑

1.Java運算符

1.1 優先級

運算符的優先級決定了一個存在多個運算符的表達式各部分的執行順序。

1.2 賦值

賦值是用等號運算符(=)進行的,它的意思是“取得右邊的值,把它複製到左邊”。右邊可以是任意常量、變量、表達式,只要能產生值就可以了,但是左邊必須是一個明確的、已命名的變量。舉個例子,可以將一個變量賦值給一個常量(A = 4),但是不能將任何東西賦值給常量(4 = A)。

對於主數據類型的賦值是非常直接的。由於主類型容納了實際的值,並不是指向一個對象的句柄,所以在賦值的時候,可以將一個地方的內容直接複製到另一個地方。例如,假設主類型使用“A = B”,那麼B處的內容就複製到了A。接着修改A的值,B不會受影響。

但是對“對象”賦值卻發生了變化。對一個對象進行操作時,實際操作的是它的句柄。例如對對象使用“C = D”,這時候C和D都指向了原來D指向的那個對象。

class Letter{
	char c;
}

public class PassObject{
	static void f(Letter y){
		y.c = ‘z’;
	}
	public static void main(String[] args){
		Letter x = new Letter();
		x.c = ‘a’;
		System.out.println(“1:x.c:” + x.c);
		f(x);
		System.out.println(“2:x.c:” + x.c);
	}
}

f()表面上是要在方法的作用域內製作一個自變量y的副本,但實際上傳遞的是一個句柄,所以y.c = 'z'實際改變的是f()之外的對象。輸出結果如下:

1:x.c:a
2:x.c:z

1.3 算數運算符

Java的基本算數運算符和其他編程語言是相同的。其中包括加號(+)、減號(-)、乘號(*)、除號(/)以及模運算符(%,從整數除法運算中獲得餘數)。整數除法會直接砍掉餘數,而不是四捨五入。

把運算符和等號連在一起用,可以在進行運算的同時進行賦值操作。例如把變量x+4並將結果賦值給x,可用x += 4。

1.4 自動遞增和遞減運算符

遞增運算符“++”意思是“增加一個單位”,遞減運算符“--”意思是“減少一個單位”。例如,A是一個int值,則表達式++A,表示A = A + 1。遞增和遞減生成的是變量的值。

對於遞增和遞減有兩個版本——“前綴版”和“後綴版”。對於前綴版,會先執行運算,再生成值;對於後綴版,會先生成值,再執行運算。下面是一個例子:

public class AutoInc {
  public static void main(String[] args) {
    int i = 1;
    prt("i : " + i);
    prt("++i : " + ++i); // Pre-increment
    prt("i++ : " + i++); // Post-increment
    prt("i : " + i);
    prt("--i : " + --i); // Pre-decrement
    prt("i-- : " + i--); // Post-decrement
    prt("i : " + i);
  }
  static void prt(String s) {
    System.out.println(s);
  }
}
則程序輸出如下:

i : 1
++i : 2
i++ : 2
i : 3
--i : 2
i-- : 2
i : 1

1.5 關係運算符

關係運算符會生成一個Boolean結果,它評價的是運算對象之間的關係。如果關係是真的,表達式會生成true,反之表達式會生成false。關係運算符有大於(>)、大於或等於(>=)、小於(<)、小於或等於(<=)、等於(==)以及不等於(!=)。其中==和!=適用於所有內建數據類型,其他的不適用於boolean類型。

1.5.1 檢查對象是否相等

public class Equivalence {
  public static void main(String[] args) {
    Integer n1 = new Integer(47);
    Integer n2 = new Integer(47);
    System.out.println(n1 == n2);
    System.out.println(n1 != n2);
  }
}
對於結果,初學者一般會認爲先打印true再打印false,因爲兩個Integer對象是相同的,但是他們錯了。儘管兩個對象內容是相同的,但是句柄是不同的,而==和!=比較的恰恰是句柄。所以輸出結果,先是false,再是true。

對於對象內容進行比較需要用到所有對象都適用的方法equals()方法,這個不適用於主類型,主類型的比較直接使用==或!=。例如:

public class EqualsMethod {
  public static void main(String[] args) {
    Integer n1 = new Integer(47);
    Integer n2 = new Integer(47);
    System.out.println(n1.equals(n2));
  }
}
輸出結果,正如我們所料輸出true。但是事情並未結束,如果是我們自己創建的類,結果會如何呢?例如:
class Value {
  int i;
}

public class EqualsMethod2 {
  public static void main(String[] args) {
    Value v1 = new Value();
    Value v2 = new Value();
    v1.i = v2.i = 100;
    System.out.println(v1.equals(v2));
  }
}
輸出結果又變成了false。這是因爲equals()默認比較的是句柄,除非在自己的類中改變了equals()的行爲,之後的文章會介紹如果改變方法的行爲。因爲大多數Java類庫都實現了equals(),使其作用比較兩個對象的內容,而不是句柄。

1.6 邏輯運算符

邏輯運算符AND(&&)、OR(||)、NOT(!)只能用於布爾值(true或者false),然後生成一個布爾值。

1.6.1 短路

操作邏輯運算符時,會遇到一種名爲“短路”的情況。這意味着只有明確得出表達式的真或假的結論時,纔會對錶達式進行邏輯求值。因此,一個表達式的各個部分都有可能不進行求值。例如如下代碼:
public class ShortCircuit {
  static boolean test1(int val) {
    System.out.println("test1(" + val + ")");
    System.out.println("result: " + (val < 1));
    return val < 1;
  }
  static boolean test2(int val) {
    System.out.println("test2(" + val + ")");
    System.out.println("result: " + (val < 2));
    return val < 2;
  }
  static boolean test3(int val) {
    System.out.println("test3(" + val + ")");
    System.out.println("result: " + (val < 3));
    return val < 3;
  }
  public static void main(String[] args) {
    if(test1(0) && test2(2) && test3(2))
      System.out.println("expression is true");
    else
      System.out.println("expression is false");
  }
}
每一次測試都會返回自變量,並比較真假。if(test1(0)) && test2(2) && test3(2)),新手一般會認爲三個測試都會執行,但是不是滴。第一個測試返回true,所以程序繼續執行。第二個測試返回false,意味着整個表達式肯定爲false,所以表達式就沒有繼續執行的必要了,剩下部分將不會執行,潛在的提高了性能。

1.7 按位運算符

按位運算符允許我們操作一個整數主數據類型中的單個“比特”,即二進制位。按位運算符會對兩個自變量中對應的位執行布爾代數,並最終生成一個結果。
按位運算來源於C語言的低級操作。我們經常都要直接操縱硬件,需要頻繁設置硬件寄存器內的二進制位。Java的設計初衷是嵌入電視頂置盒內,所以這種低級操作仍被保留下來了。然而,由於操作系統的進步,現在也許不必過於頻繁地進行按位運算。
若兩個輸入位都是1,則按位AND運算符(&)在輸出位裏生成一個1;否則生成0。若兩個輸入位裏至少有一個是1,則按位OR運算符(|)在輸出位裏生成一個1;只有在兩個輸入位都是0的情況下,它纔會生成一個0。若兩個輸入位的某一個是1,但不全都是1,那麼按位XOR(^,異或)在輸出位裏生成一個1。按位NOT(~,也叫作“非”運算符)屬於一元運算符;它只對一個自變量進行操作(其他所有運算符都是二元運算符)。按位NOT生成與輸入位的相反的值——若輸入0,則輸出1;輸入1,則輸出0。
按位運算符和邏輯運算符都使用了同樣的字符,只是數量不同。因此,我們能方便地記憶各自的含義:由於“位”是非常“小”的,所以按位運算符僅使用了一個字符。
按位運算符可與等號(=)聯合使用,以便合併運算及賦值:&=,|=和^=都是合法的(由於~是一元運算符,所以不可與=聯合使用)。
我們將boolean(布爾)類型當作一種“單位”或“單比特”值對待,所以它多少有些獨特的地方。我們可執行按位AND,OR和XOR,但不能執行按位NOT(大概是爲了避免與邏輯NOT混淆)。對於布爾值,按位運算符具有與邏輯運算符相同的效果,只是它們不會中途“短路”。此外,針對布爾值進行的按位運算爲我們新增了一個XOR邏輯運算符,它並未包括在“邏輯”運算符的列表中。在移位表達式中,我們被禁止使用布爾運算,原因將在下面解釋。

1.8 移位運算符

移位運算符面向的運算對象也是二進制的“位”。可單獨用它們處理整數類型(主類型的一種)。左移位運算符(<<)能將運算符左邊的運算對象向左移動運算符右側指定的位數(在低位補0)。“有符號”右移位運算符(>>)則將運算符左邊的運算對象向右移動運算符右側指定的位數。“有符號”右移位運算符使用了“符號擴展”:若值爲正,則在高位插入0;若值爲負,則在高位插入1。Java也添加了一種“無符號”右移位運算符(>>>),它使用了“零擴展”:無論正負,都在高位插入0。這一運算符是C或C++沒有的。
若對char,byte或者short進行移位處理,那麼在移位進行之前,它們會自動轉換成一個int。只有右側的5個低位纔會用到。這樣可防止我們在一個int數裏移動不切實際的位數。若對一個long值進行處理,最後得到的結果也是long。此時只會用到右側的6個低位,防止移動超過long值裏現成的位數。但在進行“無符號”右移位時,也可能遇到一個問題。若對byte或short值進行右移位運算,得到的可能不是正確的結果(Java 1.0和Java 1.1特別突出)。它們會自動轉換成int類型,並進行右移位。但“零擴展”不會發生,所以在那些情況下會得到-1的結果。可用下面這個例子檢測自己的實現方案:

public class URShift {
  public static void main(String[] args) {
    int i = -1;
    i >>>= 10;
<p>    System.out.println(i);</p>    long l = -1;
    l >>>= 10;
    System.out.println(l);
    short s = -1;
    s >>>= 10;
    System.out.println(s);
    byte b = -1;
    b >>>= 10;
    System.out.println(b);
  }
}

1.9 三元運算符

運算符的表達式是:布爾表達式 ? 值0 : 值1 。如果表達式的值爲true,結果爲"值0",否則爲"值1"。

1.10 逗號運算符

Java中使用逗號運算符的唯一場所是for循環,本文稍後會介紹。

1.11 字符串運算符+

這一個運算符有一個特殊的用途:用於連接不同的字符串。若表達式以一個string開頭,則之後的運算對象必須都是string類型。如下:
int x=0,y=1,z=2;
String str = "string";
System.out.println(str + x + y + z);
Java編譯程序會將x、y、z分別轉成對應的字符串形式'x'、"y"、"z",而不是將它們加在一起。如果使用System.out.println(x + str),早期的Java版本會出錯,之後版本會將x轉成"x",所以爲了避免出錯,請保證表達式的第一個對象爲string類型。

1.12 運算符的常規操作

在C和C++中,一個特別常見的錯誤如下:
while(x = y) {
//...
}
程序的意圖是測試是否“相等”(==),而不是進行賦值操作。在C和C++中,若y是一個非零值,那麼這種賦值的結果肯定是true。這樣使可能得到一個無限循環。在Java裏,這個表達式的結果並不是布爾值,而編譯器期望的是一個布爾值,而且不會從一個int數值中轉換得來。所以在編譯時,系統就會提示出現錯誤,有效地阻止我們進一步運行程序。所以這個缺點在Java裏永遠不會造成更嚴重的後果。唯一不會得到編譯錯誤的時候是x和y都爲布爾值。在這種情況下,x = y屬於合法表達式。而在上述情況下,則可能是一個錯誤。

2. 執行控制

在Java裏,涉及的關鍵字包括if-else、while、do-while、for以及一個名爲switch的選擇語句。

2.1 真和假

所有條件語句都利用條件表達式的真或假來決定執行流程。例如A == B,根據條件運算符"=="來判斷A的值是否等於B,該表達式返回true或false。本文早些說到的所有條件運算符都可以用來構造一個條件語句。

2.2 if-else

if-else是控制程序流程最基本的形式,其中else是可選的。條件表達式必須產生一個Boolean值。格式如下:
if(條件表達式)
    語句

或者另外一種形式
if(條件表達式)
    語句
else 
    語句

2.3 反覆

while、do...while、for控制着循環,有時候把它們劃分成反覆語句。除非控制反覆的布爾表達式得到"假"結果,否則語句會重複執行下去。while循環的格式如下:
while(布爾表達式)
語句

循環剛開始會計算一次表達式的值,對於之後的每一次循環,都會重新計算一次。下面舉個栗子:
public class WhileTest {
  public static void main(String[] args) {
    double r = 0;
    while(r < 0.99d) {
      r = Math.random();
      System.out.println(r);
    }
  }
} 
它用到了Math庫裏的static(靜態)方法random()。該方法的作用是產生0和1之間(包括0,但不包括1)的一個double值。while的條件表達式意思是說:“一直循環下去,直到數字等於或大於0.99”。由於它的隨機性,每運行一次這個程序,都會獲得大小不同的數字列表。

2.4 do...while

do...while格式如下:
do
語句
while(布爾表達式)

while和do...while的唯一區別是,do...while至少會執行一次,即使表達式第一次計算結果爲false,在while循環語句中,第一次表達式結果爲false,語句就不會被執行。while比do...while更常用。

2.5 for

for循環在第一次反覆之前會進行初始化。隨後它會進行條件測試,而且在每一次反覆的時候會進行某種形式的"步進"。for循環形式如下:
for(初始化表達式; 布爾表達式; 步進)
語句

初始化表達式、布爾表達式、步進都可以爲空。每次返回前都會測試一下表達式的值,若爲false,則循環結束。

2.5.1 逗號運算符

早在第1章,我們已提到了逗號運算符——注意不是逗號分隔符;後者用於分隔函數的不同自變量。Java裏唯一用到逗號運算符的地方就是for循環的控制表達式。在控制表達式的初始化和步進控制部分,我們可使用一系列由逗號分隔的語句。而且那些語句均會獨立執行。下面是一個例子:
public class CommaOperator {
  public static void main(String[] args) {
    for(int i = 1, j = i + 10; i < 5;
        i++, j = i * 2) {
      System.out.println("i= " + i + " j= " + j);
    }
  }
}
結果如下:
i= 1 j= 11
i= 2 j= 4
i= 3 j= 6
i= 4 j= 8
大家可以看到,無論在初始化還是在步進部分,語句都是順序執行的。此外,儘管初始化部分可設置任意數量的定義,但都屬於同一類型。

2.6 中斷和繼續

在任何循環語句的主體部分,亦可用break和continue控制循環的流程。其中,break用於強行退出循環,不執行循環中剩餘的語句。而continue則停止執行當前的反覆,然後退回循環起始和,開始新的反覆。
下面這個程序向大家展示了break和continue在for和while循環中的例子:
public class BreakAndContinue {
  public static void main(String[] args) {
    for(int i = 0; i < 100; i++) {
      if(i == 74) break; // Out of for loop
      if(i % 9 != 0) continue; // Next iteration
      System.out.println(i);
    }
    int i = 0;
    // An "infinite loop":
    while(true) {
      i++;
      int j = i * 27;
      if(j == 1269) break; // Out of loop
      if(i % 10 != 0) continue; // Top of loop
      System.out.println(i);
    }
  }
}

在這個for循環中,i的值永遠不會到達100。因爲一旦i到達74,break語句就會中斷循環。通常,只有在不知道中斷條件何時滿足時,才需象這樣使用break。只要i不能被9整除,continue語句會使程序流程返回循環的最開頭執行(所以使i值遞增)。如果能夠整除,則將值顯示出來。
第二部分向大家揭示了一個“無限循環”的情況。然而,循環內部有一個break語句,可中止循環。除此以外,大家還會看到continue移回循環頂部,同時不完成剩餘的內容(所以只有在i值能被9整除時纔打印出值)。輸出結果如下:

0
9
18
27
36
45
54
63
72
10
20
30
40
之所以顯示0,是由於0%9等於0。
無限循環的第二種形式是for(;;)。編譯器將while(true)與for(;;)看作同一回事。所以具體選用哪個取決於自己的編程習慣。













發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章