3.1 使用Java運算符

3.1 使用Java運算符

運算符以一個或多個自變量為基礎吏祸,可生成一個新值。自變量采用與原始方法調用不同的一種形式蹈矮,但效果是相同的泛鸟。根據(jù)以前寫程序的經(jīng)驗北滥,運算符的常規(guī)概念應該不難理解闸翅。

加號(+)坚冀、減號和負號(-)、乘號(*)司训、除號(/)以及等號(=)的用法與其他所有編程語言都是類似的壳猜。

所有運算符都能根據(jù)自己的運算對象生成一個值统扳。除此以外譬涡,一個運算符可改變運算對象的值涡匀,這叫作“副作用”(Side Effect)陨瘩。運算符最常見的用途就是修改自己的運算對象舌劳,從而產生副作用甚淡。但要注意生成的值亦可由沒有副作用的運算符生成贯卦。
幾乎所有運算符都只能操作“主類型”(Primitives)撵割。唯一的例外是“=”啡彬、“==”和“!=”庶灿,它們能操作所有對象(也是對象易令人混淆的一個地方)往踢。除此以外,String類支持“+”和“+=”妄辩。

3.1.1 優(yōu)先級

運算符的優(yōu)先級決定了存在多個運算符時一個表達式各部分的計算順序眼耀。Java對計算順序作出了特別的規(guī)定哮伟。其中楞黄,最簡單的規(guī)則就是乘法和除法在加法和減法之前完成鬼廓。程序員經(jīng)常都會忘記其他優(yōu)先級規(guī)則碎税,所以應該用括號明確規(guī)定計算順序雷蹂。例如:

A = X + Y - 2/2 + Z;

為上述表達式加上括號后匪煌,就有了一個不同的含義萎庭。

A = X + (Y - 2)/(2 + Z);

3.1.2 賦值

賦值是用等號運算符(=)進行的擎椰。它的意思是“取得右邊的值达舒,把它復制到左邊”巩搏。右邊的值可以是任何常數(shù)贯底、變量或者表達式禽捆,只要能產生一個值就行胚想。但左邊的值必須是一個明確的浊服、已命名的變量牙躺。也就是說孽拷,它必須有一個物理性的空間來保存右邊的值。舉個例子來說代虾,可將一個常數(shù)賦給一個變量(A=4;),但不可將任何東西賦給一個常數(shù)(比如不能4=A)学辱。

對主數(shù)據(jù)類型的賦值是非常直接的策泣。由于主類型容納了實際的值萨咕,而且并非指向一個對象的句柄危队,所以在為其賦值的時候茫陆,可將來自一個地方的內容復制到另一個地方簿盅。例如桨醋,假設為主類型使用“A=B”喜最,那么B處的內容就復制到A返顺。若接著又修改了A遂鹊,那么B根本不會受這種修改的影響秉扑。作為一名程序員舟陆,這應成為自己的常識秦躯。

但在為對象“賦值”的時候踱承,情況卻發(fā)生了變化茎活。對一個對象進行操作時载荔,我們真正操作的是它的句柄懒熙。所以倘若“從一個對象到另一個對象”賦值煌珊,實際就是將句柄從一個地方復制到另一個地方定庵。這意味著假若為對象使用“C=D”蔬浙,那么C和D最終都會指向最初只有D才指向的那個對象畴博。下面這個例子將向大家闡示這一點俱病。

這里有一些題外話亮隙。在后面溢吻,大家在代碼示例里看到的第一個語句將是“package 03”使用的“package”語句犀盟,它代表本書第3章阅畴。本書每一章的第一個代碼清單都會包含象這樣的一個“package”(封裝恶阴、打包冯事、包裹)語句昵仅,它的作用是為那一章剩余的代碼建立章節(jié)編號摔笤。在第17章吕世,大家會看到第3章的所有代碼清單(除那些有不同封裝名稱的以外)都會自動置入一個名為c03的子目錄里命辖;第4章的代碼置入c04;以此類推终娃。所有這些都是通過第17章展示的CodePackage.java程序實現(xiàn)的棠耕;“封裝”的基本概念會在第5章進行詳盡的解釋窍荧。就目前來說搅荞,大家只需記住象“package 03”這樣的形式只是用于為某一章的代碼清單建立相應的子目錄咕痛。

為運行程序茉贡,必須保證在classpath里包含了我們安裝本書源碼文件的根目錄(那個目錄里包含了c02放椰,c03c砾医,c04等等子目錄)如蚜。
對于Java后續(xù)的版本(1.1.4和更高版本)错邦,如果您的main()用package語句封裝到一個文件里,那么必須在程序名前面指定完整的包裹名稱魂拦,否則不能運行程序晨另。在這種情況下借尿,命令行是:

java c03.Assignment

運行位于一個“包裹”里的程序時路翻,隨時都要注意這方面的問題茂契。
下面是例子:

//: Assignment.java
// Assignment with objects is a bit tricky
package c03;

class Number {
  int i;
}

public class Assignment {
  public static void main(String[] args) {
    Number n1 = new Number();
    Number n2 = new Number();
    n1.i = 9;
    n2.i = 47;
    System.out.println("1: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
    n1 = n2;
    System.out.println("2: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
    n1.i = 27;
    System.out.println("3: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
  }
} ///:~

Number類非常簡單真竖,它的兩個實例(n1和n2)是在main()里創(chuàng)建的恢共。每個Number中的i值都賦予了一個不同的值讨韭。隨后,將n2賦給n1濒生,而且n1發(fā)生改變甜攀。在許多程序設計語言中,我們都希望n1和n2任何時候都相互獨立瘦麸。但由于我們已賦予了一個句柄滋饲,所以下面才是真實的輸出:

1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27

看來改變n1的同時也改變了n2屠缭!這是由于無論n1還是n2都包含了相同的句柄呵曹,它指向相同的對象(最初的句柄位于n1內部,指向容納了值9的一個對象跨新。在賦值過程中域帐,那個句柄實際已經(jīng)丟失肖揣;它的對象會由“垃圾收集器”自動清除)阳欲。
這種特殊的現(xiàn)象通常也叫作“別名”球化,是Java操作對象的一種基本方式。但假若不愿意在這種情況下出現(xiàn)別名巢掺,又該怎么操作呢陆淀?可放棄賦值轧苫,并寫入下述代碼:

n1.i = n2.i;

這樣便可保留兩個獨立的對象,而不是將n1和n2綁定到相同的對象岔乔。但您很快就會意識到雏门,這樣做會使對象內部的字段處理發(fā)生混亂,并與標準的面向對象設計準則相悖呼胚。由于這并非一個簡單的話題蝇更,所以留待第12章詳細論述蚁廓,那一章是專門討論別名的相嵌。其時饭宾,大家也會注意到對象的賦值會產生一些令人震驚的效果格了。

1. 方法調用中的別名處理

將一個對象傳遞到方法內部時看铆,也會產生別名現(xiàn)象。

//: PassObject.java
// Passing objects to methods can be a bit tricky

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()方法表面上似乎要在方法的作用域內制作自己的自變量Letter y的一個副本弹惦。但同樣地悄但,實際傳遞的是一個句柄棠隐。所以下面這個程序行:

y.c = 'z';

實際改變的是f()之外的對象。輸出結果如下:

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

別名和它的對策是非常復雜的一個問題算墨。盡管必須等至第12章才可獲得所有答案宵荒,但從現(xiàn)在開始就應加以重視,以便提早發(fā)現(xiàn)它的缺點净嘀。

3.1.3 算術運算符

Java的基本算術運算符與其他大多數(shù)程序設計語言是相同的。其中包括加號(+)侠讯、減號(-)挖藏、除號(/)、乘號(*)以及模數(shù)(%厢漩,從整數(shù)除法中獲得余數(shù))膜眠。整數(shù)除法會直接砍掉小數(shù),而不是進位溜嗜。

Java也用一種簡寫形式進行運算宵膨,并同時進行賦值操作。這是由等號前的一個運算符標記的炸宵,而且對于語言中的所有運算符都是固定的辟躏。例如,為了將4加到變量x土全,并將結果賦給x捎琐,可用:x+=4会涎。

下面這個例子展示了算術運算符的各種用法:

//: MathOps.java
// Demonstrates the mathematical operators
import java.util.*;

public class MathOps {
  // Create a shorthand to save typing:
  static void prt(String s) {
    System.out.println(s);
  }
  // shorthand to print a string and an int:
  static void pInt(String s, int i) {
    prt(s + " = " + i);
  }
  // shorthand to print a string and a float:
  static void pFlt(String s, float f) {
    prt(s + " = " + f);
  }
  public static void main(String[] args) {
    // Create a random number generator,
    // seeds with current time by default:
    Random rand = new Random();
    int i, j, k;
    // '%' limits maximum value to 99:
    j = rand.nextInt() % 100;
    k = rand.nextInt() % 100;
    pInt("j",j);  pInt("k",k);
    i = j + k; pInt("j + k", i);
    i = j - k; pInt("j - k", i);
    i = k / j; pInt("k / j", i);
    i = k * j; pInt("k * j", i);
    i = k % j; pInt("k % j", i);
    j %= k; pInt("j %= k", j);
    // Floating-point number tests:
    float u,v,w;  // applies to doubles, too
    v = rand.nextFloat();
    w = rand.nextFloat();
    pFlt("v", v); pFlt("w", w);
    u = v + w; pFlt("v + w", u);
    u = v - w; pFlt("v - w", u);
    u = v * w; pFlt("v * w", u);
    u = v / w; pFlt("v / w", u);
    // the following also works for
    // char, byte, short, int, long,
    // and double:
    u += v; pFlt("u += v", u);
    u -= v; pFlt("u -= v", u);
    u *= v; pFlt("u *= v", u);
    u /= v; pFlt("u /= v", u);
  }
} ///:~

我們注意到的第一件事情就是用于打印(顯示)的一些快捷方法:prt()方法打印一個String瑞凑;pInt()先打印一個String末秃,再打印一個int;而pFlt()先打印一個String籽御,再打印一個float练慕。當然,它們最終都要用System.out.println()結尾技掏。

為生成數(shù)字铃将,程序首先會創(chuàng)建一個Random(隨機)對象。由于自變量是在創(chuàng)建過程中傳遞的零截,所以Java將當前時間作為一個“種子值”麸塞,由隨機數(shù)生成器利用。通過Random對象涧衙,程序可生成許多不同類型的隨機數(shù)字哪工。做法很簡單,只需調用不同的方法即可:nextInt()弧哎,nextLong()雁比,nextFloat()或者nextDouble()。

若隨同隨機數(shù)生成器的結果使用撤嫩,模數(shù)運算符(%)可將結果限制到運算對象減1的上限(本例是99)之下偎捎。

1. 一元加、減運算符

一元減號(-)和一元加號(+)與二元加號和減號都是相同的運算符序攘。根據(jù)表達式的書寫形式茴她,編譯器會自動判斷使用哪一種。例如下述語句:

x = -a;

它的含義是顯然的程奠。編譯器能正確識別下述語句:

x = a * -b;

但讀者會被搞糊涂丈牢,所以最好更明確地寫成:

x = a * (-b);

一元減號得到的運算對象的負值。一元加號的含義與一元減號相反瞄沙,雖然它實際并不做任何事情己沛。

3.1.4 自動遞增和遞減

和C類似,Java提供了豐富的快捷運算方式距境。這些快捷運算可使代碼更清爽申尼,更易錄入,也更易讀者辨讀垫桂。

兩種很不錯的快捷運算方式是遞增和遞減運算符(常稱作“自動遞增”和“自動遞減”運算符)师幕。其中,遞減運算符是“--”伪货,意為“減少一個單位”们衙;遞增運算符是“++”钾怔,意為“增加一個單位”。舉個例子來說蒙挑,假設A是一個int(整數(shù))值宗侦,則表達式++A就等價于(A = A + 1)。遞增和遞減運算符結果生成的是變量的值忆蚀。

對每種類型的運算符矾利,都有兩個版本可供選用;通常將其稱為“前綴版”和“后綴版”馋袜∧衅欤“前遞增”表示++運算符位于變量或表達式的前面;而“后遞增”表示++運算符位于變量或表達式的后面欣鳖。類似地察皇,“前遞減”意味著--運算符位于變量或表達式的前面媒役;而“后遞減”意味著--運算符位于變量或表達式的后面姓言。對于前遞增和前遞減(如++A或--A),會先執(zhí)行運算商虐,再生成值怀酷。而對于后遞增和后遞減(如A++或A--)稻爬,會先生成值,再執(zhí)行運算蜕依。下面是一個例子:

//: AutoInc.java
// Demonstrates the ++ and -- operators

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

從中可以看到桅锄,對于前綴形式,我們在執(zhí)行完運算后才得到值样眠。但對于后綴形式友瘤,則是在運算執(zhí)行之前就得到值。它們是唯一具有“副作用”的運算符(除那些涉及賦值的以外)檐束。也就是說商佑,它們會改變運算對象,而不僅僅是使用自己的值厢塘。
遞增運算符正是對“C++”這個名字的一種解釋,暗示著“超載C的一步”肌幽。在早期的一次Java演講中晚碾,Bill Joy(始創(chuàng)人之一)聲稱“Java=C++--”(C加加減減),意味著Java已去除了C++一些沒來由折磨人的地方喂急,形成一種更精簡的語言格嘁。正如大家會在這本書中學到的那樣,Java的許多地方都得到了簡化廊移,所以Java的學習比C++更容易糕簿。

3.1.5 關系運算符

關系運算符生成的是一個“布爾”(Boolean)結果探入。它們評價的是運算對象值之間的關系。若關系是真實的懂诗,關系表達式會生成true(真)蜂嗽;若關系不真實,則生成false(假)殃恒。關系運算符包括小于(<)植旧、大于(>)、小于或等于(<=)离唐、大于或等于(>=)病附、等于(==)以及不等于(!=)。等于和不等于適用于所有內建的數(shù)據(jù)類型亥鬓,但其他比較不適用于boolean類型完沪。

1. 檢查對象是否相等

關系運算符==和!=也適用于所有對象,但它們的含義通常會使初涉Java領域的人找不到北嵌戈。下面是一個例子:

//: Equivalence.java

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);
  }
} ///:~

其中覆积,表達式System.out.println(n1 == n2)可打印出內部的布爾比較結果。一般人都會認為輸出結果肯定先是true咕别,再是false技健,因為兩個Integer對象都是相同的。但盡管對象的內容相同惰拱,句柄卻是不同的雌贱,而==和!=比較的正好就是對象句柄。所以輸出結果實際上先是false偿短,再是true欣孤。這自然會使第一次接觸的人感到驚奇。

若想對比兩個對象的實際內容是否相同昔逗,又該如何操作呢降传?此時,必須使用所有對象都適用的特殊方法equals()勾怒。但這個方法不適用于“主類型”婆排,那些類型直接使用==和!=即可。下面舉例說明如何使用:

//: EqualsMethod.java

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段只。但事情并未到此結束!假設您創(chuàng)建了自己的類鉴扫,就象下面這樣:

//: EqualsMethod2.java

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(),否則不可能表現(xiàn)出我們希望的行為炕婶。不幸的是姐赡,要到第7章才會學習如何改變行為。但要注意equals()的這種行為方式同時或許能夠避免一些“災難”性的事件柠掂。

大多數(shù)Java類庫都實現(xiàn)了equals()项滑,所以它實際比較的是對象的內容,而非它們的句柄陪踩。

3.1.6 邏輯運算符

邏輯運算符AND(&&)杖们、OR(||)以及NOT(!)能生成一個布爾值(true或false)——以自變量的邏輯關系為基礎。下面這個例子向大家展示了如何使用關系和邏輯運算符肩狂。

//: Bool.java
// Relational and logical operators
import java.util.*;

public class Bool {
  public static void main(String[] args) {
    Random rand = new Random();
    int i = rand.nextInt() % 100;
    int j = rand.nextInt() % 100;
    prt("i = " + i);
    prt("j = " + j);
    prt("i > j is " + (i > j));
    prt("i < j is " + (i < j));
    prt("i >= j is " + (i >= j));
    prt("i <= j is " + (i <= j));
    prt("i == j is " + (i == j));
    prt("i != j is " + (i != j));

    // Treating an int as a boolean is 
    // not legal Java
//! prt("i && j is " + (i && j));
//! prt("i || j is " + (i || j));
//! prt("!i is " + !i);

    prt("(i < 10) && (j < 10) is "
       + ((i < 10) && (j < 10)) );
    prt("(i < 10) || (j < 10) is "
       + ((i < 10) || (j < 10)) );
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~

只可將AND摘完,OR或NOT應用于布爾值。與在C及C++中不同傻谁,不可將一個非布爾值當作布爾值在邏輯表達式中使用孝治。若這樣做,就會發(fā)現(xiàn)嘗試失敗审磁,并用一個“//!”標出谈飒。然而,后續(xù)的表達式利用關系比較生成布爾值态蒂,然后對結果進行邏輯運算杭措。
輸出列表看起來象下面這個樣子:

i = 85
j = 4
i > j is true
i < j is false
i >= j is true
i <= j is false
i == j is false
i != j is true
(i < 10) && (j < 10) is false
(i < 10) || (j < 10) is true

注意若在預計為String值的地方使用,布爾值會自動轉換成適當?shù)奈谋拘问健?/p>

在上述程序中钾恢,可將對int的定義替換成除boolean以外的其他任何主數(shù)據(jù)類型手素。但要注意,對浮點數(shù)字的比較是非常嚴格的瘩蚪。即使一個數(shù)字僅在小數(shù)部分與另一個數(shù)字存在極微小的差異泉懦,仍然認為它們是“不相等”的。即使一個數(shù)字只比零大一點點(例如2不停地開平方根)疹瘦,它仍然屬于“非零”值崩哩。

1. 短路

操作邏輯運算符時,我們會遇到一種名為“短路”的情況言沐。這意味著只有明確得出整個表達式真或假的結論邓嘹,才會對表達式進行邏輯求值。因此险胰,一個邏輯表達式的所有部分都有可能不進行求值:

//: ShortCircuit.java
// Demonstrates short-circuiting behavior
// with logical operators.

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))

很自然地,你也許認為所有這三個測試都會得以執(zhí)行。但希望輸出結果不至于使你大吃一驚:

if(test1(0) && test2(2) && test3(2))

第一個測試生成一個true結果缨睡,所以表達式求值會繼續(xù)下去鸟悴。然而,第二個測試產生了一個false結果奖年。由于這意味著整個表達式肯定為false细诸,所以為什么還要繼續(xù)剩余的表達式呢?這樣做只會徒勞無益陋守。事實上震贵,“短路”一詞的由來正種因于此。如果一個邏輯表達式的所有部分都不必執(zhí)行下去水评,那么潛在的性能提升將是相當可觀的猩系。

3.1.7 按位運算符

按位運算符允許我們操作一個整數(shù)主數(shù)據(jù)類型中的單個“比特”,即二進制位中燥。按位運算符會對兩個自變量中對應的位執(zhí)行布爾代數(shù)寇甸,并最終生成一個結果。

按位運算來源于C語言的低級操作疗涉。我們經(jīng)常都要直接操縱硬件拿霉,需要頻繁設置硬件寄存器內的二進制位。Java的設計初衷是嵌入電視頂置盒內咱扣,所以這種低級操作仍被保留下來了绽淘。然而,由于操作系統(tǒng)的進步闹伪,現(xiàn)在也許不必過于頻繁地進行按位運算沪铭。

若兩個輸入位都是1,則按位AND運算符(&)在輸出位里生成一個1祭往;否則生成0伦意。若兩個輸入位里至少有一個是1,則按位OR運算符(|)在輸出位里生成一個1硼补;只有在兩個輸入位都是0的情況下驮肉,它才會生成一個0。若兩個輸入位的某一個是1已骇,但不全都是1离钝,那么按位XOR(^,異或)在輸出位里生成一個1褪储。按位NOT(~卵渴,也叫作“非”運算符)屬于一元運算符;它只對一個自變量進行操作(其他所有運算符都是二元運算符)鲤竹。按位NOT生成與輸入位的相反的值——若輸入0浪读,則輸出1;輸入1,則輸出0碘橘。

按位運算符和邏輯運算符都使用了同樣的字符互订,只是數(shù)量不同。因此痘拆,我們能方便地記憶各自的含義:由于“位”是非逞銮荩“小”的,所以按位運算符僅使用了一個字符纺蛆。

按位運算符可與等號(=)聯(lián)合使用吐葵,以便合并運算及賦值:&=,|=和^=都是合法的(由于~是一元運算符桥氏,所以不可與=聯(lián)合使用)温峭。

我們將boolean(布爾)類型當作一種“單位”或“單比特”值對待,所以它多少有些獨特的地方识颊。我們可執(zhí)行按位AND诚镰,OR和XOR,但不能執(zhí)行按位NOT(大概是為了避免與邏輯NOT混淆)祥款。對于布爾值清笨,按位運算符具有與邏輯運算符相同的效果,只是它們不會中途“短路”刃跛。此外抠艾,針對布爾值進行的按位運算為我們新增了一個XOR邏輯運算符,它并未包括在“邏輯”運算符的列表中桨昙。在移位表達式中检号,我們被禁止使用布爾運算,原因將在下面解釋蛙酪。

3.1.8 移位運算符

移位運算符面向的運算對象也是二進制的“位”齐苛。可單獨用它們處理整數(shù)類型(主類型的一種)桂塞。左移位運算符(<<)能將運算符左邊的運算對象向左移動運算符右側指定的位數(shù)(在低位補0)凹蜂。“有符號”右移位運算符(>>)則將運算符左邊的運算對象向右移動運算符右側指定的位數(shù)阁危÷耆“有符號”右移位運算符使用了“符號擴展”:若值為正,則在高位插入0狂打;若值為負擂煞,則在高位插入1。Java也添加了一種“無符號”右移位運算符(>>>)趴乡,它使用了“零擴展”:無論正負对省,都在高位插入0蝗拿。這一運算符是C或C++沒有的。

若對char官辽,byte或者short進行移位處理蛹磺,那么在移位進行之前,它們會自動轉換成一個int同仆。只有右側的5個低位才會用到。這樣可防止我們在一個int數(shù)里移動不切實際的位數(shù)裙品。若對一個long值進行處理俗批,最后得到的結果也是long。此時只會用到右側的6個低位市怎,防止移動超過long值里現(xiàn)成的位數(shù)岁忘。但在進行“無符號”右移位時,也可能遇到一個問題区匠。若對byte或short值進行右移位運算干像,得到的可能不是正確的結果(Java 1.0和Java 1.1特別突出)。它們會自動轉換成int類型驰弄,并進行右移位麻汰。但“零擴展”不會發(fā)生,所以在那些情況下會得到-1的結果戚篙∥弼辏可用下面這個例子檢測自己的實現(xiàn)方案:

//: URShift.java
// Test of unsigned right shift

public class URShift {
  public static void main(String[] args) {
    int i = -1;
    i >>>= 10;
    System.out.println(i);
    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);
  }
} ///:~

移位可與等號(<<=或>>=或>>>=)組合使用。此時岔擂,運算符左邊的值會移動由右邊的值指定的位數(shù)位喂,再將得到的結果賦回左邊的值。

下面這個例子向大家闡示了如何應用涉及“按位”操作的所有運算符乱灵,以及它們的效果:

//: BitManipulation.java
// Using the bitwise operators
import java.util.*;

public class BitManipulation {
  public static void main(String[] args) {
    Random rand = new Random();
    int i = rand.nextInt();
    int j = rand.nextInt();
    pBinInt("-1", -1);
    pBinInt("+1", +1);
    int maxpos = 2147483647;
    pBinInt("maxpos", maxpos);
    int maxneg = -2147483648;
    pBinInt("maxneg", maxneg);
    pBinInt("i", i);
    pBinInt("~i", ~i);
    pBinInt("-i", -i);
    pBinInt("j", j);
    pBinInt("i & j", i & j);
    pBinInt("i | j", i | j);
    pBinInt("i ^ j", i ^ j);
    pBinInt("i << 5", i << 5);
    pBinInt("i >> 5", i >> 5);
    pBinInt("(~i) >> 5", (~i) >> 5);
    pBinInt("i >>> 5", i >>> 5);
    pBinInt("(~i) >>> 5", (~i) >>> 5);

    long l = rand.nextLong();
    long m = rand.nextLong();
    pBinLong("-1L", -1L);
    pBinLong("+1L", +1L);
    long ll = 9223372036854775807L;
    pBinLong("maxpos", ll);
    long lln = -9223372036854775808L;
    pBinLong("maxneg", lln);
    pBinLong("l", l);
    pBinLong("~l", ~l);
    pBinLong("-l", -l);
    pBinLong("m", m);
    pBinLong("l & m", l & m);
    pBinLong("l | m", l | m);
    pBinLong("l ^ m", l ^ m);
    pBinLong("l << 5", l << 5);
    pBinLong("l >> 5", l >> 5);
    pBinLong("(~l) >> 5", (~l) >> 5);
    pBinLong("l >>> 5", l >>> 5);
    pBinLong("(~l) >>> 5", (~l) >>> 5);
  }
  static void pBinInt(String s, int i) {
    System.out.println(
      s + ", int: " + i + ", binary: ");
    System.out.print("   ");
    for(int j = 31; j >=0; j--)
      if(((1 << j) &  i) != 0)
        System.out.print("1");
      else
        System.out.print("0");
    System.out.println();
  }
  static void pBinLong(String s, long l) {
    System.out.println(
      s + ", long: " + l + ", binary: ");
    System.out.print("   ");
    for(int i = 63; i >=0; i--)
      if(((1L << i) & l) != 0)
        System.out.print("1");
      else
        System.out.print("0");
    System.out.println();
  }
} ///:~

程序末尾調用了兩個方法:pBinInt()和pBinLong()塑崖。它們分別操作一個int和long值,并用一種二進制格式輸出痛倚,同時附有簡要的說明文字规婆。目前,可暫時忽略它們具體的實現(xiàn)方案状原。

大家要注意的是System.out.print()的使用聋呢,而不是System.out.println()。print()方法不會產生一個新行颠区,以便在同一行里羅列多種信息削锰。

除展示所有按位運算符針對int和long的效果之外,本例也展示了int和long的最小值毕莱、最大值器贩、+1和-1值颅夺,使大家能體會它們的情況。注意高位代表正負號:0為正蛹稍,1為負吧黄。下面列出int部分的輸出:

-1, int: -1, binary: 
   11111111111111111111111111111111
+1, int: 1, binary: 
   00000000000000000000000000000001
maxpos, int: 2147483647, binary: 
   01111111111111111111111111111111
maxneg, int: -2147483648, binary: 
   10000000000000000000000000000000
i, int: 59081716, binary: 
   00000011100001011000001111110100
~i, int: -59081717, binary: 
   11111100011110100111110000001011
-i, int: -59081716, binary: 
   11111100011110100111110000001100
j, int: 198850956, binary: 
   00001011110110100011100110001100
i & j, int: 58720644, binary: 
   00000011100000000000000110000100
i | j, int: 199212028, binary: 
   00001011110111111011101111111100
i ^ j, int: 140491384, binary: 
   00001000010111111011101001111000
i << 5, int: 1890614912, binary: 
   01110000101100000111111010000000
i >> 5, int: 1846303, binary: 
   00000000000111000010110000011111
(~i) >> 5, int: -1846304, binary: 
   11111111111000111101001111100000
i >>> 5, int: 1846303, binary: 
   00000000000111000010110000011111
(~i) >>> 5, int: 132371424, binary: 
   00000111111000111101001111100000

數(shù)字的二進制形式表現(xiàn)為“有符號2的補值”。

3.1.9 三元if-else運算符

這種運算符比較罕見唆姐,因為它有三個運算對象拗慨。但它確實屬于運算符的一種,因為它最終也會生成一個值奉芦。這與本章后一節(jié)要講述的普通if-else語句是不同的赵抢。表達式采取下述形式:

布爾表達式 ? 值0:值1

若“布爾表達式”的結果為true,就計算“值0”声功,而且它的結果成為最終由運算符產生的值烦却。但若“布爾表達式”的結果為false,計算的就是“值1”先巴,而且它的結果成為最終由運算符產生的值其爵。

當然,也可以換用普通的if-else語句(在后面介紹)伸蚯,但三元運算符更加簡潔摩渺。盡管C引以為傲的就是它是一種簡練的語言,而且三元運算符的引入多半就是為了體現(xiàn)這種高效率的編程朝卒,但假若您打算頻繁用它证逻,還是要先多作一些思量——它很容易就會產生可讀性極差的代碼。

可將條件運算符用于自己的“副作用”抗斤,或用于它生成的值囚企。但通常都應將其用于值,因為那樣做可將運算符與if-else明確區(qū)別開瑞眼。下面便是一個例子:

static int ternary(int i) {
return i < 10 ? i * 100 : i * 10;
}

可以看出龙宏,假設用普通的if-else結構寫上述代碼,代碼量會比上面多出許多伤疙。如下所示:

static int alternative(int i) {
if (i < 10)
return i * 100;
return i * 10;
}

但第二種形式更易理解银酗,而且不要求更多的錄入。所以在挑選三元運算符時徒像,請務必權衡一下利弊黍特。

3.1.10 逗號運算符

在C和C++里,逗號不僅作為函數(shù)自變量列表的分隔符使用锯蛀,也作為進行后續(xù)計算的一個運算符使用灭衷。在Java里需要用到逗號的唯一場所就是for循環(huán),本章稍后會對此詳加解釋旁涤。

3.1.11 字串運算符+

這個運算符在Java里有一項特殊用途:連接不同的字串翔曲。這一點已在前面的例子中展示過了迫像。盡管與+的傳統(tǒng)意義不符,但用+來做這件事情仍然是非常自然的瞳遍。在C++里闻妓,這一功能看起來非常不錯,所以引入了一項“運算符過載”機制掠械,以便C++程序員為幾乎所有運算符增加特殊的含義由缆。但非常不幸,與C++的另外一些限制結合猾蒂,運算符過載成為一種非常復雜的特性犁功,程序員在設計自己的類時必須對此有周到的考慮。與C++相比婚夫,盡管運算符過載在Java里更易實現(xiàn),但迄今為止仍然認為這一特性過于復雜署鸡。所以Java程序員不能象C++程序員那樣設計自己的過載運算符案糙。

我們注意到運用“String +”時一些有趣的現(xiàn)象。若表達式以一個String起頭靴庆,那么后續(xù)所有運算對象都必須是字串时捌。如下所示:

int x = 0, y = 1, z = 2;
String sString = "x, y, z ";
System.out.println(sString + x + y + z);

在這里,Java編譯程序會將x炉抒,y和z轉換成它們的字串形式奢讨,而不是先把它們加到一起。然而焰薄,如果使用下述語句:

System.out.println(x + sString);

那么早期版本的Java就會提示出錯(以后的版本能將x轉換成一個字串)拿诸。因此,如果想通過“加號”連接字串(使用Java的早期版本)塞茅,請務必保證第一個元素是字串(或加上引號的一系列字符亩码,編譯能將其識別成一個字串)。

3.1.12 運算符常規(guī)操作規(guī)則

使用運算符的一個缺點是括號的運用經(jīng)常容易搞錯野瘦。即使對一個表達式如何計算有絲毫不確定的因素描沟,都容易混淆括號的用法。這個問題在Java里仍然存在鞭光。
在C和C++中吏廉,一個特別常見的錯誤如下:

while(x = y) {
//...
}

程序的意圖是測試是否“相等”(==),而不是進行賦值操作惰许。在C和C++中席覆,若y是一個非零值,那么這種賦值的結果肯定是true啡省。這樣使可能得到一個無限循環(huán)娜睛。在Java里髓霞,這個表達式的結果并不是布爾值,而編譯器期望的是一個布爾值畦戒,而且不會從一個int數(shù)值中轉換得來方库。所以在編譯時,系統(tǒng)就會提示出現(xiàn)錯誤障斋,有效地阻止我們進一步運行程序纵潦。所以這個缺點在Java里永遠不會造成更嚴重的后果。唯一不會得到編譯錯誤的時候是x和y都為布爾值垃环。在這種情況下邀层,x = y屬于合法表達式。而在上述情況下遂庄,則可能是一個錯誤寥院。

在C和C++里,類似的一個問題是使用按位AND和OR涛目,而不是邏輯AND和OR秸谢。按位AND和OR使用兩個字符之一(&或|),而邏輯AND和OR使用兩個相同的字符(&&或||)霹肝。就象“=”和“==”一樣估蹄,鍵入一個字符當然要比鍵入兩個簡單。在Java里沫换,編譯器同樣可防止這一點臭蚁,因為它不允許我們強行使用一種并不屬于的類型。

3.1.13 造型運算符

“造型”(Cast)的作用是“與一個模型匹配”讯赏。在適當?shù)臅r候垮兑,Java會將一種數(shù)據(jù)類型自動轉換成另一種。例如待逞,假設我們?yōu)楦↑c變量分配一個整數(shù)值甥角,計算機會將int自動轉換成float。通過造型识樱,我們可明確設置這種類型的轉換嗤无,或者在一般沒有可能進行的時候強迫它進行。

為進行一次造型怜庸,要將括號中希望的數(shù)據(jù)類型(包括所有修改符)置于其他任何值的左側当犯。下面是一個例子:

void casts() {
int i = 200;
long l = (long)i;
long l2 = (long)200;
}

正如您看到的那樣,既可對一個數(shù)值進行造型處理割疾,亦可對一個變量進行造型處理嚎卫。但在這兒展示的兩種情況下,造型均是多余的,因為編譯器在必要的時候會自動進行int值到long值的轉換拓诸。當然侵佃,仍然可以設置一個造型,提醒自己留意奠支,也使程序更清楚馋辈。在其他情況下,造型只有在代碼編譯時才顯出重要性倍谜。

在C和C++中迈螟,造型有時會讓人頭痛。在Java里尔崔,造型則是一種比較安全的操作答毫。但是,若進行一種名為“縮小轉換”(Narrowing Conversion)的操作(也就是說季春,腳本是能容納更多信息的數(shù)據(jù)類型洗搂,將其轉換成容量較小的類型),此時就可能面臨信息丟失的危險载弄。此時蚕脏,編譯器會強迫我們進行造型,就好象說:“這可能是一件危險的事情——如果您想讓我不顧一切地做侦锯,那么對不起,請明確造型秦驯〕吲觯”而對于“放大轉換”(Widening conversion),則不必進行明確造型译隘,因為新類型肯定能容納原來類型的信息亲桥,不會造成任何信息的丟失。

Java允許我們將任何主類型“造型”為其他任何一種主類型固耘,但布爾值(bollean)要除外题篷,后者根本不允許進行任何造型處理√浚“類”不允許進行造型番枚。為了將一種類轉換成另一種,必須采用特殊的方法(字串是一種特殊的情況损敷,本書后面會講到將對象造型到一個類型“家族”里葫笼;例如,“橡樹”可造型為“樹”拗馒;反之亦然路星。但對于其他外來類型,如“巖石”诱桂,則不能造型為“樹”)洋丐。

1. 字面值

最開始的時候呈昔,若在一個程序里插入“字面值”(Literal),編譯器通常能準確知道要生成什么樣的類型友绝。但在有些時候堤尾,對于類型卻是曖昧不清的。若發(fā)生這種情況九榔,必須對編譯器加以適當?shù)摹爸笇А卑Ь7椒ㄊ怯门c字面值關聯(lián)的字符形式加入一些額外的信息。下面這段代碼向大家展示了這些字符哲泊。

//: Literals.java

class Literals {
  char c = 0xffff; // max char hex value
  byte b = 0x7f; // max byte hex value
  short s = 0x7fff; // max short hex value
  int i1 = 0x2f; // Hexadecimal (lowercase)
  int i2 = 0X2F; // Hexadecimal (uppercase)
  int i3 = 0177; // Octal (leading zero)
  // Hex and Oct also work with long.
  long n1 = 200L; // long suffix
  long n2 = 200l; // long suffix
  long n3 = 200;
  //! long l6(200); // not allowed
  float f1 = 1;
  float f2 = 1F; // float suffix
  float f3 = 1f; // float suffix
  float f4 = 1e-45f; // 10 to the power
  float f5 = 1e+9f; // float suffix
  double d1 = 1d; // double suffix
  double d2 = 1D; // double suffix
  double d3 = 47e47d; // 10 to the power
} ///:~

十六進制(Base 16)——它適用于所有整數(shù)數(shù)據(jù)類型——用一個前置的0x或0X指示剩蟀。并在后面跟隨采用大寫或小寫形式的0-9以及a-f。若試圖將一個變量初始化成超出自身能力的一個值(無論這個值的數(shù)值形式如何)切威,編譯器就會向我們報告一條出錯消息育特。注意在上述代碼中,最大的十六進制值只會在char先朦,byte以及short身上出現(xiàn)缰冤。若超出這一限制,編譯器會將值自動變成一個int喳魏,并告訴我們需要對這一次賦值進行“縮小造型”棉浸。這樣一來,我們就可清楚獲知自己已超載了邊界刺彩。

八進制(Base 8)是用數(shù)字中的一個前置0以及0-7的數(shù)位指示的迷郑。在C,C++或者Java中创倔,對二進制數(shù)字沒有相應的“字面”表示方法嗡害。

字面值后的尾隨字符標志著它的類型。若為大寫或小寫的L畦攘,代表long霸妹;大寫或小寫的F,代表float知押;大寫或小寫的D叹螟,則代表double。

指數(shù)總是采用一種我們認為很不直觀的記號方法:1.39e-47f台盯。在科學與工程學領域首妖,“e”代表自然對數(shù)的基數(shù),約等于2.718(Java一種更精確的double值采用Math.E的形式)爷恳。它在象“1.39×e的-47次方”這樣的指數(shù)表達式中使用有缆,意味著“1.39×2.718的-47次方”。然而,自FORTRAN語言發(fā)明后棚壁,人們自然而然地覺得e代表“10多少次冪”杯矩。這種做法顯得頗為古怪,因為FORTRAN最初面向的是科學與工程設計領域袖外。理所當然史隆,它的設計者應對這樣的混淆概念持謹慎態(tài)度(注釋①)。但不管怎樣曼验,這種特別的表達方法在C泌射,C++以及現(xiàn)在的Java中頑固地保留下來了。所以倘若您習慣將e作為自然對數(shù)的基數(shù)使用鬓照,那么在Java中看到象“1.39e-47f”這樣的表達式時熔酷,請轉換您的思維,從程序設計的角度思考它豺裆;它真正的含義是“1.39×10的-47次方”拒秘。

①:John Kirkham這樣寫道:“我最早于1962年在一部IBM 1620機器上使用FORTRAN II。那時——包括60年代以及70年代的早期,F(xiàn)ORTRAN一直都是使用大寫字母。之所以會出現(xiàn)這一情況渐白,可能是由于早期的輸入設備大多是老式電傳打字機,使用5位Baudot碼金砍,那種碼并不具備小寫能力。乘冪表達式中的‘E’也肯定是大寫的,所以不會與自然對數(shù)的基數(shù)‘e’發(fā)生沖突,后者必然是小寫的量愧。‘E’這個字母的含義其實很簡單帅矗,就是‘Exponential’的意思,即‘指數(shù)’或‘冪數(shù)’煞烫,代表計算系統(tǒng)的基數(shù)——一般都是10浑此。當時,八進制也在程序員中廣泛使用滞详。盡管我自己未看到它的使用凛俱,但假若我在乘冪表達式中看到一個八進制數(shù)字,就會把它認作Base 8料饥。我記得第一次看到用小寫‘e’表示指數(shù)是在70年代末期蒲犬。我當時也覺得它極易產生混淆。所以說岸啡,這個問題完全是自己‘潛入’FORTRAN里去的原叮,并非一開始就有。如果你真的想使用自然對數(shù)的基數(shù),實際有現(xiàn)成的函數(shù)可供利用奋隶,但它們都是大寫的擂送。”

注意如果編譯器能夠正確地識別類型唯欣,就不必使用尾隨字符嘹吨。對于下述語句:

long n3 = 200;

它并不存在含混不清的地方,所以200后面的一個L大可省去境氢。然而蟀拷,對于下述語句:

float f4 = 1e-47f; //10的冪數(shù)

編譯器通常會將指數(shù)作為雙精度數(shù)(double)處理,所以假如沒有這個尾隨的f萍聊,就會收到一條出錯提示问芬,告訴我們須用一個“造型”將double轉換成float。

2. 轉型

大家會發(fā)現(xiàn)假若對主數(shù)據(jù)類型執(zhí)行任何算術或按位運算脐区,只要它們“比int小”(即char愈诚,byte或者short),那么在正式執(zhí)行運算之前牛隅,那些值會自動轉換成int炕柔。這樣一來,最終生成的值就是int類型媒佣。所以只要把一個值賦回較小的類型匕累,就必須使用“造型”。此外默伍,由于是將值賦回給較小的類型欢嘿,所以可能出現(xiàn)信息丟失的情況)。通常也糊,表達式中最大的數(shù)據(jù)類型是決定了表達式最終結果大小的那個類型炼蹦。若將一個float值與一個double值相乘,結果就是double狸剃;如將一個int和一個long值相加掐隐,則結果為long。

3.1.14 Java沒有“sizeof”

在C和C++中钞馁,sizeof()運算符能滿足我們的一項特殊需要:獲知為數(shù)據(jù)項目分配的字符數(shù)量虑省。在C和C++中,size()最常見的一種應用就是“移植”僧凰。不同的數(shù)據(jù)在不同的機器上可能有不同的大小探颈,所以在進行一些對大小敏感的運算時,程序員必須對那些類型有多大做到心中有數(shù)训措。例如伪节,一臺計算機可用32位來保存整數(shù)光羞,而另一臺只用16位保存。顯然架馋,在第一臺機器中狞山,程序可保存更大的值。正如您可能已經(jīng)想到的那樣叉寂,移植是令C和C++程序員頗為頭痛的一個問題萍启。
Java不需要sizeof()運算符來滿足這方面的需要,因為所有數(shù)據(jù)類型在所有機器的大小都是相同的屏鳍。我們不必考慮移植問題——Java本身就是一種“與平臺無關”的語言勘纯。

3.1.15 復習計算順序

在我舉辦的一次培訓班中,有人抱怨運算符的優(yōu)先順序太難記了钓瞭。一名學生推薦用一句話來幫助記憶:“Ulcer Addicts Really Like C A lot”驳遵,即“潰瘍患者特別喜歡(維生素)C”。

助記詞 運算符類型 運算符
Ulcer Unary + - ++ – [[ rest...]]
Addicts Arithmetic (and shift) * / % + - << >>
Really Relational > < >= <= == !=
Like Logical (and bitwise) ** && & ^ **
C Conditional (ternary) A > B ? X : Y
A Lot Assignment = (and compound assignment like *=)

當然山涡,對于移位和按位運算符堤结,上表并不是完美的助記方法;但對于其他運算來說鸭丛,它確實很管用竞穷。

3.1.16 運算符總結
下面這個例子向大家展示了如何隨同特定的運算符使用主數(shù)據(jù)類型。從根本上說鳞溉,它是同一個例子反反復復地執(zhí)行瘾带,只是使用了不同的主數(shù)據(jù)類型。文件編譯時不會報錯熟菲,因為那些會導致錯誤的行已用//!變成了注釋內容看政。

//: AllOps.java
// Tests all the operators on all the
// primitive data types to show which
// ones are accepted by the Java compiler.

class AllOps {
  // To accept the results of a boolean test:
  void f(boolean b) {}
  void boolTest(boolean x, boolean y) {
    // Arithmetic operators:
    //! x = x * y;
    //! x = x / y;
    //! x = x % y;
    //! x = x + y;
    //! x = x - y;
    //! x++;
    //! x--;
    //! x = +y;
    //! x = -y;
    // Relational and logical:
    //! f(x > y);
    //! f(x >= y);
    //! f(x < y);
    //! f(x <= y);
    f(x == y);
    f(x != y);
    f(!y);
    x = x && y;
    x = x || y;
    // Bitwise operators:
    //! x = ~y;
    x = x & y;
    x = x | y;
    x = x ^ y;
    //! x = x << 1;
    //! x = x >> 1;
    //! x = x >>> 1;
    // Compound assignment:
    //! x += y;
    //! x -= y;
    //! x *= y;
    //! x /= y;
    //! x %= y;
    //! x <<= 1;
    //! x >>= 1;
    //! x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! char c = (char)x;
    //! byte B = (byte)x;
    //! short s = (short)x;
    //! int i = (int)x;
    //! long l = (long)x;
    //! float f = (float)x;
    //! double d = (double)x;
  }
  void charTest(char x, char y) {
    // Arithmetic operators:
    x = (char)(x * y);
    x = (char)(x / y);
    x = (char)(x % y);
    x = (char)(x + y);
    x = (char)(x - y);
    x++;
    x--;
    x = (char)+y;
    x = (char)-y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x= (char)~y;
    x = (char)(x & y);
    x  = (char)(x | y);
    x = (char)(x ^ y);
    x = (char)(x << 1);
    x = (char)(x >> 1);
    x = (char)(x >>> 1);
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void byteTest(byte x, byte y) {
    // Arithmetic operators:
    x = (byte)(x* y);
    x = (byte)(x / y);
    x = (byte)(x % y);
    x = (byte)(x + y);
    x = (byte)(x - y);
    x++;
    x--;
    x = (byte)+ y;
    x = (byte)- y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x = (byte)~y;
    x = (byte)(x & y);
    x = (byte)(x | y);
    x = (byte)(x ^ y);
    x = (byte)(x << 1);
    x = (byte)(x >> 1);
    x = (byte)(x >>> 1);
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void shortTest(short x, short y) {
    // Arithmetic operators:
    x = (short)(x * y);
    x = (short)(x / y);
    x = (short)(x % y);
    x = (short)(x + y);
    x = (short)(x - y);
    x++;
    x--;
    x = (short)+y;
    x = (short)-y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x = (short)~y;
    x = (short)(x & y);
    x = (short)(x | y);
    x = (short)(x ^ y);
    x = (short)(x << 1);
    x = (short)(x >> 1);
    x = (short)(x >>> 1);
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void intTest(int x, int y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x = ~y;
    x = x & y;
    x = x | y;
    x = x ^ y;
    x = x << 1;
    x = x >> 1;
    x = x >>> 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void longTest(long x, long y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    x = ~y;
    x = x & y;
    x = x | y;
    x = x ^ y;
    x = x << 1;
    x = x >> 1;
    x = x >>> 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x <<= 1;
    x >>= 1;
    x >>>= 1;
    x &= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    float f = (float)x;
    double d = (double)x;
  }
  void floatTest(float x, float y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    //! x = ~y;
    //! x = x & y;
    //! x = x | y;
    //! x = x ^ y;
    //! x = x << 1;
    //! x = x >> 1;
    //! x = x >>> 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    //! x <<= 1;
    //! x >>= 1;
    //! x >>>= 1;
    //! x &= y;
    //! x ^= y;
    //! x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    double d = (double)x;
  }
  void doubleTest(double x, double y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x > y);
    f(x >= y);
    f(x < y);
    f(x <= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x && y);
    //! f(x || y);
    // Bitwise operators:
    //! x = ~y;
    //! x = x & y;
    //! x = x | y;
    //! x = x ^ y;
    //! x = x << 1;
    //! x = x >> 1;
    //! x = x >>> 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    //! x <<= 1;
    //! x >>= 1;
    //! x >>>= 1;
    //! x &= y;
    //! x ^= y;
    //! x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
  }
} ///:~

注意布爾值(boolean)的能力非常有限。我們只能為其賦予true和false值抄罕。而且可測試它為真還是為假允蚣,但不可為它們再添加布爾值,或進行其他其他任何類型運算呆贿。
在char嚷兔,byte和short中,我們可看到算術運算符的“轉型”效果榨崩。對這些類型的任何一個進行算術運算,都會獲得一個int結果章母。必須將其明確“造型”回原來的類型(縮小轉換會造成信息的丟失)母蛛,以便將值賦回那個類型。但對于int值乳怎,卻不必進行造型處理彩郊,因為所有數(shù)據(jù)都已經(jīng)屬于int類型前弯。然而,不要放松警惕秫逝,認為一切事情都是安全的恕出。如果對兩個足夠大的int值執(zhí)行乘法運算,結果值就會溢出违帆。下面這個例子向大家展示了這一點:

//: Overflow.java
// Surprise! Java lets you overflow.

public class Overflow {
  public static void main(String[] args) {
    int big = 0x7fffffff; // max int value
    prt("big = " + big);
    int bigger = big * 4;
    prt("bigger = " + bigger);
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~

輸出結果如下:

big = 2147483647
bigger = -4

而且不會從編譯器那里收到出錯提示浙巫,運行時也不會出現(xiàn)異常反應。爪哇咖啡(Java)確實是很好的東西刷后,但卻沒有“那么”好的畴!
對于char,byte或者short尝胆,混合賦值并不需要造型丧裁。即使它們執(zhí)行轉型操作,也會獲得與直接算術運算相同的結果含衔。而在另一方面煎娇,將造型略去可使代碼顯得更加簡練。

大家可以看到贪染,除boolean以外缓呛,任何一種主類型都可通過造型變?yōu)槠渌黝愋汀M瑯拥匾纸斣煨统梢环N較小的類型時强经,必須留意“縮小轉換”的后果。否則會在造型過程中不知不覺地丟失信息寺渗。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末匿情,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子信殊,更是在濱河造成了極大的恐慌炬称,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涡拘,死亡現(xiàn)場離奇詭異玲躯,居然都是意外死亡,警方通過查閱死者的電腦和手機鳄乏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門跷车,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人橱野,你說我怎么就攤上這事朽缴。” “怎么了水援?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵密强,是天一觀的道長茅郎。 經(jīng)常有香客問我,道長或渤,這世上最難降的妖魔是什么系冗? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮薪鹦,結果婚禮上掌敬,老公的妹妹穿的比我還像新娘。我一直安慰自己距芬,他們只是感情好涝开,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著框仔,像睡著了一般舀武。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上离斩,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天银舱,我揣著相機與錄音,去河邊找鬼跛梗。 笑死寻馏,一個胖子當著我的面吹牛,可吹牛的內容都是我干的核偿。 我是一名探鬼主播诚欠,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼漾岳!你這毒婦竟也來了轰绵?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤尼荆,失蹤者是張志新(化名)和其女友劉穎左腔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捅儒,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡液样,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了巧还。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞭莽。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖麸祷,靈堂內的尸體忽然破棺而出澎怒,到底是詐尸還是另有隱情,我是刑警寧澤摇锋,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布丹拯,位于F島的核電站,受9級特大地震影響荸恕,放射性物質發(fā)生泄漏乖酬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一融求、第九天 我趴在偏房一處隱蔽的房頂上張望咬像。 院中可真熱鬧,春花似錦生宛、人聲如沸县昂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倒彰。三九已至,卻和暖如春莱睁,著一層夾襖步出監(jiān)牢的瞬間待讳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工仰剿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留创淡,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓南吮,卻偏偏與公主長得像琳彩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子部凑,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內容