筆記

          way

          Java Puzzlers(三-一 循環)

          24     
            for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
                  if (b == 0x90)
                      System.out.print("Joy!");
              }
          Ox90 超過了byte的取值范圍-128到127。byte和int比較是一種混合類型比較。考慮個表達式((byte)0x90 == 0x90)得到的是false。byte和int做比較的時候,Java先對byte進行了widening primitive conversion 再比較兩個int值。因為byte是有符號類型,轉變做了符號擴展,把負的byte值轉換為相應的int值。這個例子中(byte)0x90被轉變為-112,當然不等于int值 0x90或者說+144。混合比較總讓人迷惑,因為總是強迫系統去提升一個操作數來和另一種類型匹配。有幾種方式可避免混合比較。可以把int映射為byte,之后比較兩個byte值:
          if (b == (byte)0x90)
              System.out.println("Joy!");
          另外,可用mask抑制符號擴展,把byte轉換為int,之后比較兩個int值:
          if ((b & 0xff) == 0x90)
              System.out.println("Joy!");
          但最好的方法是把常量值移出循環放到常量聲明中。
              private static final byte TARGET = 0x90; // Broken!
              public static void main(String[] args) {
                  for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++)
                      if (b == TARGET)
                          System.out.print("Joy!");
              }
          不幸的是,上面編譯通不過:0x90對于byte類型來說不是一個有效值。這樣修改即可:
          private static final byte TARGET = (byte)0x90;

          To summarize: Avoid mixed-type comparisons, because they are inherently confusing (Puzzle 5). To help achieve this goal, use declared constants in place of "magic numbers." You already knew that this was a good idea; it documents the meanings of constants, centralizes their definitions, and eliminates duplicate definitions.現在你知道他還可以強制你為每一個常量定義適用的類型,避免一種混合類型比較的來源。

          25
          int j = 0;
          for (int i = 0; i < 100; i++)
            j = j++;
          System.out.println(j); //
          打印出的是0

          問題出在 j = j++; 等同于下列操作:

          int tmp = j; j = j + 1; j = tmp;
          這次的教訓和難題7一樣:在一個表達式中不要給同一個變量賦值超過一次。

          26

          public static final int END = Integer.MAX_VALUE;
          public static final int START = END - 100;
          public static void main(String[] args) {
              int count = 0;
              for (int i = START; i <= END; i++)
                 count++;
               System.out.println(count);
          }
          看起來像100,再仔細看循環是小于等于,應該是101?結果是程序沒有輸出任何值,陷入一個死循環。問題出在Integer.MAX_VALUE,當繼續增加的時候,他悄悄變為Integer.MIN_VALUE。如果你需要循環int值邊界,最好用long變量做索引:
          for (long i = START; i <= END; i++)  //輸出101
          教訓是:ints are not integers。無論何時用基本類型,注意邊界值。上溢或下溢會出現什么情況?一般來說最好用大一點的類型(基本類型是byte, char, short, int, and long)。也可以不用long:

          int i = START;
          do {
            count++;
          } while (i++ != END);
          考慮到清晰和簡單,總是用long索引,除了一種特殊情況:如果要遍歷所有int值,這樣用int索引的話會快兩倍。
          一個循環四十億int值,調用方法的常規用法:

          // Apply the function f to all four billion int values
          int i = Integer.MIN_VALUE;
          do {
          f(i);
          } while (i++ != Integer.MAX_VALUE);
          

          27  位移
          記住java是使用二進制補碼計算,在任何有符號基本類型中(byte, short, int, or long)都是用所有位置1來表示-1。
                  int i = 0;
                  while (-1 << i != 0)       //左位移
                      i++;
                  System.out.println(i);
          int型的-1用0xffffffff 表示。不斷左移,右邊由0補位。移位32次,變為全0,跳出循環打印32?實際上程序會死循環。問題出在-1<<32不等于0而是等于-1,因為位移符號只用右邊操作數的低五位作為移動距離,如果左操作數是long的話用六位。三個位移操作符:<<,>>,>>>都是這樣。移動距離總是0到31,左邊操作數是long的話0到63。位移距離用32取模,左邊是long則用64取模。給int值位移32位或給long值位移64位只會返回本身。所以不可能用位移完全移除一個數據。幸運的是,有一個簡單的辦法解決這個問題。保存上一次的位移結果,每一次迭代多移動一位。
                  int distance = 0;
                  for (int val = -1; val != 0; val <<= 1)
                      distance++;
                  System.out.println(distance);
          修改后的程序說明了一個原則:位移距離如果可能的話,用常量
          另外一個問題,許多程序員認為右移一個負的移動距離,就和左移一樣,反之亦然。事實上不是這樣,左移是左移,右移就是右移。負數距離只留下低五位(long留六位),其余的置0就變為了正數距離。比如,左移一個int值-1的距離,實際上是左移31位。

          28 無窮的表示
          for (int i = start; i <= start + 1; i++) {
          }
          看起來循環兩次就會結束,如果這樣定義呢:
          int start = Integer.MAX_VALUE - 1;//死循環
          while (i == i + 1) {
          }
          這個不可能死循環?如果i是無窮呢?Java采用IEEE 754浮點數算術,用double或float來表示無窮。所以可以用任何浮點數計算表達式得出無窮來初始化i。比如:double i = 1.0 / 0.0;
          更好的是可以利用標準庫提供的常量:double i = Double.POSITIVE_INFINITY;
          事實上,根本用不著用無窮初始化i來引起死循環。只要足夠大的浮點數就夠了:double i = 1.0e40;
          這是因為浮點數越大,他的值和下一個數的值距離也就越大。distribution of floating-point values is a consequence of their representation with a fixed number of significant bits. 給足夠大的浮點數加一不會改變值,因為他不能填充這個數和下一個數之間的距離。浮點數操作返回最接近準確數學結果的浮點值。一旦兩個相鄰浮點值之間的距離大于2,加1就不會有效果。float類型來說,超過225(或33,554,432)再加1就無效;對double來說超過254(接近1.8 x 1016)再加1就無效。
               相鄰浮點數之間的距離稱為ulp(unit in the last place的縮寫)。在Java 5里 Math.ulp方法被引入來計算float或double值的ulp。

          總結:不可能用float或double來表示無窮。另外,在一個大的浮點數上加一個小的浮點數,值不會改變。有點不合常理,實數并不是這樣。記住二進制浮點數計算只是近似于實數計算。

          29 NaN

          while (i != i) { }
          IEEE 754 浮點數計算保留了一個特殊值來來表示不是數字的數量。NaN是浮點計算不能很好定義的數,比如0.0 / 0.0。規范定義NaN不等于任何數包括自己。因此double i = 0.0 / 0.0; 可讓開始的等式不成立。也有標準庫定義的常量:double i = Double.NaN; 如果一個或多個操作數為NaN,那么浮點數計算就會等于NaN。

          總結:float和doule存在特殊的值NaN,小心處理。

          30
          while (i != i + 0) { } 這一次不能使用float或者double。
          +操作符除了數字,就只能處理String。+操作符會被重載:對于String類型,他做的是連接操作。如果操作數有非String類型,會先做轉換變為String之后再做連接。i一般用作數字,要是對String型變量這么命名容易引起誤解。

          總結:操作符重載非常容易誤導人。好的變量名,方法名,類名和好的注釋對于程序的可讀性一樣重要。

          posted on 2010-11-15 16:39 yuxh 閱讀(380) 評論(0)  編輯  收藏 所屬分類: jdk

          導航

          <2010年11月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          統計

          常用鏈接

          留言簿

          隨筆分類

          隨筆檔案

          收藏夾

          博客

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 华安县| 佛冈县| 库尔勒市| 深水埗区| 盐池县| 阳谷县| 罗平县| 贵德县| 井冈山市| 防城港市| 昌吉市| 麦盖提县| 江城| 宝清县| 万荣县| 漳平市| 正镶白旗| 平定县| 辛集市| 治多县| 蓬安县| 石首市| 鄂托克旗| 旬邑县| 淮滨县| 江源县| 万源市| 霍林郭勒市| 大城县| 龙州县| 满城县| 岳西县| 会理县| 库尔勒市| 桐柏县| 玉溪市| 华池县| 铜山县| 铁岭市| 太仓市| 四平市|