程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java不變字串

java不變字串

編輯:關於JAVA

請觀察下述代碼:
 

//: Stringer.java

public class Stringer {
  static String upcase(String s) {
    return s.toUpperCase();
  }
  public static void main(String[] args) {
    String q = new String("howdy");
    System.out.println(q); // howdy
    String qq = upcase(q);
    System.out.println(qq); // HOWDY
    System.out.println(q); // howdy
  }
} ///:~

q傳遞進入upcase()時,它實際是q的句柄的一個副本。該句柄連接的對象實際只在一個統一的物理位置處。句柄四處傳遞的時候,它的句柄會得到復制。
若觀察對upcase()的定義,會發現傳遞進入的句柄有一個名字s,而且該名字只有在upcase()執行期間才會存在。upcase()完成後,本地句柄s便會消失,而upcase()返回結果——還是原來那個字串,只是所有字符都變成了大寫。當然,它返回的實際是結果的一個句柄。但它返回的句柄最終是為一個新對象的,同時原來的q並未發生變化。所有這些是如何發生的呢?

1. 隱式常數
若使用下述語句:
String s = "asdf";
String x = Stringer.upcase(s);
那麼真的希望upcase()方法改變自變量或者參數嗎?我們通常是不願意的,因為作為提供給方法的一種信息,自變量一般是拿給代碼的讀者看的,而不是讓他們修改。這是一個相當重要的保證,因為它使代碼更易編寫和理解。
為了在C++中實現這一保證,需要一個特殊關鍵字的幫助:const。利用這個關鍵字,程序員可以保證一個句柄(C++叫“指針”或者“引用”)不會被用來修改原始的對象。但這樣一來,C++程序員需要用心記住在所有地方都使用const。這顯然易使人混淆,也不容易記住。

2. 覆蓋"+"和StringBuffer
利用前面提到的技術,String類的對象被設計成“不可變”。若查閱聯機文檔中關於String類的內容(本章稍後還要總結它),就會發現類中能夠修改String的每個方法實際都創建和返回了一個嶄新的String對象,新對象裡包含了修改過的信息——原來的String是原封未動的。因此,Java裡沒有與C++的const對應的特性可用來讓編譯器支持對象的不可變能力。若想獲得這一能力,可以自行設置,就象String那樣。
由於String對象是不可變的,所以能夠根據情況對一個特定的String進行多次別名處理。因為它是只讀的,所以一個句柄不可能會改變一些會影響其他句柄的東西。因此,只讀對象可以很好地解決別名問題。
通過修改產生對象的一個嶄新版本,似乎可以解決修改對象時的所有問題,就象String那樣。但對某些操作來講,這種方法的效率並不高。一個典型的例子便是為String對象覆蓋的運算符“+”。“覆蓋”意味著在與一個特定的類使用時,它的含義已發生了變化(用於String的“+”和“+=”是Java中能被覆蓋的唯一運算符,Java不允許程序員覆蓋其他任何運算符——注釋④)。

④:C++允許程序員隨意覆蓋運算符。由於這通常是一個復雜的過程(參見《Thinking in C++》,Prentice-Hall於1995年出版),所以Java的設計者認定它是一種“糟糕”的特性,決定不在Java中采用。但具有諷剌意味的是,運算符的覆蓋在Java中要比在C++中容易得多。

針對String對象使用時,“+”允許我們將不同的字串連接起來:
 

String s = "abc" + foo + "def" + Integer.toString(47);

可以想象出它“可能”是如何工作的:字串"abc"可以有一個方法append(),它新建了一個字串,其中包含"abc"以及foo的內容;這個新字串然後再創建另一個新字串,在其中添加"def";以此類推。
這一設想是行得通的,但它要求創建大量字串對象。盡管最終的目的只是獲得包含了所有內容的一個新字串,但中間卻要用到大量字串對象,而且要不斷地進行垃圾收集。我懷疑Java的設計者是否先試過種方法(這是軟件開發的一個教訓——除非自己試試代碼,並讓某些東西運行起來,否則不可能真正了解系統)。我還懷疑他們是否早就發現這樣做獲得的性能是不能接受的。
解決的方法是象前面介紹的那樣制作一個可變的同志類。對字串來說,這個同志類叫作StringBuffer,編譯器可以自動創建一個StringBuffer,以便計算特定的表達式,特別是面向String對象應用覆蓋過的運算符+和+=時。下面這個例子可以解決這個問題:
 

//: ImmutableStrings.java
// Demonstrating StringBuffer

public class ImmutableStrings {
  public static void main(String[] args) {
    String foo = "foo";
    String s = "abc" + foo +
      "def" + Integer.toString(47);
    System.out.println(s);
    // The "equivalent" using StringBuffer:
    StringBuffer sb = 
      new StringBuffer("abc"); // Creates String!
    sb.append(foo);
    sb.append("def"); // Creates String!
    sb.append(Integer.toString(47));
    System.out.println(sb);
  }
} ///:~

創建字串s時,編譯器做的工作大致等價於後面使用sb的代碼——創建一個StringBuffer,並用append()將新字符直接加入StringBuffer對象(而不是每次都產生新對象)。盡管這樣做更有效,但不值得每次都創建象"abc"和"def"這樣的引號字串,編譯器會把它們都轉換成String對象。所以盡管StringBuffer提供了更高的效率,但會產生比我們希望的多得多的對象。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved