筆記

          way

          Java Puzzlers(二-一 字符)

          11 字符相加
                System.out.print("H" + "a");System.out.print('H' + 'a'); //貌似輸出HaHa?
                 最后輸出的是Ha169'H''a' 都是 char,不是String,+操作符做的是加法操作而不是拼接字符串。編譯器提升兩個char值到int值,從16位零擴展到32位的int。一個是72另一個是97。從語義上說,char值和字符串的相似是非常迷惑的。java語言僅僅把char看做無符號的16位基本整數。庫不這樣認為,他里面有許多方法把char參數當作Unicode字符在處理。怎樣連接字符?可以使用庫方法,如:
               StringBuffer sb = new StringBuffer();
               sb.append('H');sb.append('a');
               System.out.println(sb);  //可行但丑陋
              有很多方法避免這種繁瑣,只要一個操作符是string就能強制+操作符做拼接操作。習慣用法是在前面加一個空字符串“”,如:
               System.out.print("" + 'H' + 'a');//雖然有用,但還是有點不雅而且容易導致一點困惑 
               試試:System.out.println("2 + 2 = " + 2+2);  如果用的是java 5還可以使用printf:System.out.printf("%c%c", 'H', 'a');
                總結:小心應對字符串拼接操作符。“+”只有至少一個是String的時候才做字符串拼接;否則做加法。如果沒有String型,有幾個選擇:加空字符串;用String.valueOf把第一個值顯示轉換為String;用String buffer;java 5的話用printf
          12  字符數組
               String letters = "ABC";
               char[] numbers = { '1', '2', '3' };
               System.out.println(letters + " easy as " + numbers);  //返回ABC easy as [C@16f0472
           char 雖是基本整數類型,但char值常常表示字符而不是整數,很多庫對他特殊對待。比如,把char傳給println 輸出的是Unicode 字符而不是數字碼。char數組獲得同樣對待:char[] 的重載是println輸出數組中的所有字符,char[]對String.valueOfStringBuffer.append 的重載也類似。但是字符拼接操作符不是像這些方法,他是對兩邊做字符串轉換然后再拼接。對于對象引用包括數組,字符串轉換是這樣定義的:如果應用是null,轉換為字符串"null",否則調用被引用對象的toString 無參數方法;如果toString 方法返回的是null,還是用“null”字符串。非null的char數組調用toString做什么操作?數組從Object 繼承來的toString,定義,“返回字符串含有對象實例類名,字符'@',對象的用無符號十六進制表示的hash碼”。 Class.getName 的說明表示對 char[] 該類對象調用該方法返回 "[C"。兩個方法修正,拼接之前可顯示轉換數組為String:
               System.out.println(letters + " easy as " + String.valueOf(numbers));
          還可以把the System.out.println 分開成兩個來利用 char[] 對println的重載:
               System.out.print(letters + " easy as ");System.out.println(numbers);
           注意這些修正只是在你正確重載valueOfprintln方法才起效。換句話說,他們嚴重依賴編譯時數組引用的類型。 下面例子看起來用來第二個修正方法,但還是輸出丑陋字符串。因為他調用了Object對println的重載而不是char[]的重載。
          // Broken - invokes the wrong overloading of println!
          class Abc {
              public static void main(String[] args) {
                  String letters = "ABC";
                  Object numbers = new char[] { '1', '2', '3' };
                  System.out.print(letters + " easy as ");
                  System.out.println(numbers); // Invokes println(Object)
              }
          }
          總結:char數組不是字符串。把char數組轉換為字符串,調用 String.valueOf(char[])。一些庫方法給char數組提供了像字符串的支持,典型的是給Object提供一個重載再給char[]提供一個重載;后一個才是理想的操作。

          13 Interning 
                final String pig = "length: 10";
                final String dog = "length: " + pig.length();
                System.out.println("Animals are equal: "  + pig == dog);
          compile-time constants of type String are interned.換句話說,任何兩個有相同字符的String類型的常量表達式是同一個對象引用表示的。所以如果用常量表達式初始化的話,pig和dog會指向同一個對象,但dog沒用常量表達式。Java語言限制哪些操作可以出現在常量表達式中,方法調用是不允許的。因此,程序應該輸出 Animals are equal: false,對吧?事實上不是,運行發現只輸出false 。操作符的優先級體現出來,事實上是
               System.out.println(("Animals are equal: " + pig) == dog);
          有一種方法能避免這種困難:但使用字符拼接操作符的時候,總是給重要操作數加上括號:System.out.println("Animals are equal: " + (pig == dog));
          辯證的說,這樣還是有問題。Your code should rarely, if ever, depend on the interning of string constants。Interning 只是用來減少虛擬機內存的,不是用來當作程序員工具的。由于字符串intern 失敗帶來的bug非常難以檢測。對比兩個對象引用的時候,應該用equals方法而不是==操作符除非你是想比較對象identity而不是值。所以我們的例子應該這樣:System.out.println("Animals are equal: " + pig.equals(dog));

          14 轉義符

              下面程序使用 Unicode escapes:用他們的十六進制數字碼表示Unicode 字符。
              // \u0022 is the Unicode escape for double quote (")
              System.out.println("a\u0022.length() + \u0022b".length());
            Java provides no special treatment for Unicode escapes within string literals。編譯器在把Unicode escapes程序解析為字符串之前,先變為了他們表示的字符。可以使用escape sequences:即用\"表示雙引號 ,例System.out.println("a\".length() + \"b".length()); 
          還有很多escape sequences : single quote (\'), linefeed (\n), tab (\t), and backslash (\\).  escape sequences 等程序先解析為符號之后才處理。ASCII是Unicode的子集。ASCII是最小的字符集,只有128個字符,Unicode有 65,000的字符。Unicode escape 能被用來把Unicode 字符插入只能使用ASCII字符的程序中。一個 Unicode escape意味著和他代表的字符完全相同的東西。但程序員用源文件的字符集不能插入一些字符的時候,可以使用 Unicode escape,主要是把非ASCII字符變為標志符,字符串,注釋等。
            總結:在字符串和字符文字中,用escape sequences不用Unicode escapes 。不要用Unicode escapes  表示ASCII字符。在字符串和字符文字中,用escape sequences;在外面的話直接把ASCII 字符插入源文件。

          15 
               Unicode escapes must be well formed, even if they appear in comments. 下面這個例子編譯出錯
          /**
          * Generated by the IBM IDL-to-Java compiler, version 1.0
          * from F:\TestRoot\apps\a1\units\include\PolicyHome.idl
          * Wednesday, June 17, 1998 6:44:40 o'clock AM GMT+00:00
          */
          工具在把Windows 文件名放入注釋之前,必須把\去掉。
          總之,\u不要出現在有效Unicode escape范圍之外,即使注釋也不行。特別是自動產生代碼的時候。
          16 
              line separator 用來表示分割文本行的字符,每個平臺的line separator 不一樣。Windows 上,CR character (carriage return) followed by the LF character (linefeed)。UNIX上只有LF字符(經常被較為newline character)。下面把這些字符傳給println
             // Note: \u000A is Unicode representation of linefeed (LF)
              char c = 0x000A;
              System.out.println(c);
          結果編譯錯誤!仍然是由于注釋中Unicode escape,編譯器再拋棄注釋內容和空格之前就把Unicode escapes 轉換為字符。\u000A 代表linefeed character,因此程序最后轉換為 
              // Note:
             is Unicode representation of linefeed (LF)
             char c = 0x000A;
             System.out.println(c);
          最簡單的修改方法是去掉 Unicode escape ,但更好的方法是用escape sequence 初始化c,而不是用十六進制整數,排除注釋的需要
             char c = '\n';
             System.out.println(c);
          這樣改后程序還是有問題,有平臺依賴。在某些平臺,如UNIX,他將輸出兩行完整的分割符;另外一些平臺,如Windows,則不會。雖然肉眼看起來一樣,但如果存在一個文件或管道里供其他程序處理話 是很容易出問題的。 如果打算輸出兩行空白,應該調用println兩次。Java 5,你可以使用printf帶上格式"%n%n"來代替println,每一個出現的 %n是printf打印出平臺相應的行分隔符。
          道理:非必需盡量不用Unicode escapes
          17 
             Unicode escapes are essential when you need to insert characters that can't be represented in any other way into your program. Avoid them in all other cases.Unicode escapes 減少程序的清晰性,增加bug的出現。對語言設計者來說,應該使Unicode escapes表示ASCII字符非法。
          18 字符集
            byte bytes[] = new byte[256];
            for(int i = 0; i < 256; i++)
              bytes[i] = (byte)i;
            String str = new String(bytes);
            for(int i = 0, n = str.length(); i < n; i++)
              System.out.print((int)str.charAt(i) + " ");
            在不同的機器上運行,結果完全不一樣。原因是String(byte[])構造器。規范說“用平臺默認的字符集給指定的byte數組解碼創建一個新字符串。新字符串的長度是字符集的功能,因此可能和byte數組的長度不一樣。當給定的字節在默認字符集中無效時,該構造器行為不確定”。什么是字符集?技術上說,他是“the combination of a coded character set and a character-encoding scheme”。換句話說,是一堆字符,表達字符的數字編碼,以及一序列字符編碼和字節互相轉換的方法。轉換方案在不同的字符集中差異巨大。一些字符和字節一一對應;大多數不這樣。只有默認字符集是 ISO-8859-1(更多被稱為Latin-1 )的時候上面的程序才會輸出0到255的整數。J2SE運行環境的默認編碼是由底層操作系統和區域決定的。在早期的JAVA版本讀取系統屬性 "file.encoding"來得到JRE默認編碼,JAVA 5后以后的版本,可使用 java.nio.charset.Charset.defaultCharset()方法。幸運的是,你不是非得要面對默認字符集的變化多端。char序列和byte序列互相轉換的時候,你可以并且大多數時候應當顯式的指定字符集。一個以字符集名字和byte數組為參數的String構造器可完成此任務。用下面方法,上面程序就不會受默認字符集的影響了:String str = new String(bytes, "ISO-8859-1"); 該構造器拋出UnsupportedEncodingException,你必須捕獲,當更好的方法是聲明一個main方法來拋出他否則不能編譯通過。事實上,上面的程序不會拋出異常。因為Charset 的規范指明任何JAVA平臺的實現必須支持某些字符集,ISO-8859-1是其中之一。
            教訓:每次從byte序列轉換為String,不管顯示或隱式都在使用一種字符集。如果想程序不出意外,每次使用時顯示指定一種字符集。

          posted on 2010-10-22 08:49 yuxh 閱讀(159) 評論(0)  編輯  收藏 所屬分類: jdk

          導航

          <2010年10月>
          262728293012
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          統計

          常用鏈接

          留言簿

          隨筆分類

          隨筆檔案

          收藏夾

          博客

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 容城县| 五大连池市| 黄冈市| 饶河县| 舞阳县| 鄂尔多斯市| 饶平县| 潞城市| 赤壁市| 且末县| 长海县| 石城县| 罗田县| 沐川县| 仲巴县| 金阳县| 牟定县| 象州县| 宜宾市| 曲阜市| 昌图县| 基隆市| 双流县| 玉环县| 勐海县| 蛟河市| 雷波县| 招远市| 竹北市| 绩溪县| 辰溪县| 鹤岗市| 龙州县| 永寿县| 滨海县| 濮阳县| 武汉市| 彰化县| 阜康市| 伽师县| 马龙县|