簡體   English   中英

Java 中表達式“new String(...)”的目的是什么?

[英]What is the purpose of the expression “new String(…)” in Java?

在查看在線代碼示例時,我有時會遇到通過使用 new 運算符將 String 常量分配給 String 對象的情況。

例如:

String s;
...
s = new String("Hello World");

當然,這相比

s = "Hello World";

我不熟悉這種語法,也不知道目的或效果是什么。 由於字符串常量通常存儲在常量池中,然后以 JVM 用於處理字符串常量的任何表示形式存儲,是否會在堆上分配任何內容?

您可能認為需要new String(String)的一個地方是強制內部字符數組的不同副本,如

small=new String(huge.substring(10,20))

然而,不幸的是,這種行為沒有記錄並且依賴於實現。

在將大文件(一些高達 20 MiB)讀取到一個字符串中並在事后將其雕刻成行時,我已經被這個燒毀了。 我最終得到了引用由整個文件組成的 char[] 的行的所有字符串。 不幸的是,這無意中保留了對整個數組的引用,因為我堅持的幾行比處理文件的時間更長 - 我被迫使用new String()來解決它,因為處理 20,000 個文件很快消耗了大量資源內存量。

唯一實現不可知的方法是:

small=new String(huge.substring(10,20).toCharArray());

不幸的是,這必須復制數組兩次,一次用於toCharArray() ,一次在 String 構造函數中。

需要有記錄的方法來通過復制現有字符串的字符來獲取新字符串; 或者String(String)的文檔需要改進以使其更加明確(那里有暗示,但它相當模糊且易於解釋)。

假設文檔沒有說明的陷阱

為了回應不斷出現的評論,請觀察new String()的 Apache Harmony 實現是什么:

public String(String string) {
    value = string.value;
    offset = string.offset;
    count = string.count;
}

沒錯,那里沒有底層數組的副本。 然而,它仍然符合 (Java 7) String 文檔,因為它:

初始化一個新創建的 String 對象,使其表示與參數相同的字符序列; 換句話說,新創建的字符串是參數字符串的副本。 除非需要原始的顯式副本,否則不需要使用此構造函數,因為字符串是不可變的。

突出的部分是“參數字符串的副本”; 它沒有說“參數字符串的副本和支持該字符串的底層字符數組”。

請注意您對文檔進行編程,而不是一種實現

我發現這很有用的唯一一次是在聲明鎖變量時:

private final String lock = new String("Database lock");

....

synchronized(lock)
{
    // do something
}

在這種情況下,Eclipse 等調試工具會在列出線程當前持有或等待的鎖時顯示字符串。 您必須使用“新字符串”,即分配一個新的字符串對象,否則共享字符串文字可能會被鎖定在其他一些不相關的代碼中。

Software Monkey 和 Ruggs 描述的這個構造函數的唯一實用程序似乎已經從 JDK7 中消失了。 類String中不再有offset字段,子串一直使用

Arrays.copyOfRange(char[] original, int from, int to) 

修剪副本的字符數組。

字符串 s1="foo"; 文字將進入 StringPool 並且 s1 將引用。

字符串 s2="foo"; 這次它將檢查“foo”文字是否已經在 StringPool 中可用,因為它現在存在,因此 s2 將引用相同的文字。

String s3=new String("foo"); “foo”文字將首先在 StringPool 中創建,然后通過字符串 arg 構造函數創建字符串對象,即由於通過 new 運算符創建對象而在堆中創建“foo”,然后 s3 將引用它。

String s4=new String("foo"); 與 s3 相同

所以System.out.println(s1==s2); // 由於文字比較而為

System.out.println(s3==s4); // false由於對象比較(s3 和 s4 在堆中的不同位置創建)

嗯,這取決於示例中的“...”是什么。 例如,如果它是一個 StringBuffer,或者一個字節數組,或者其他東西,你會得到一個由你傳遞的數據構造的 String。

但是如果它只是另一個字符串,如new String("Hello World!") ,那么它應該被簡單地替換為"Hello World!" ,在所有情況下。 字符串是不可變的,所以克隆一個沒有任何意義——創建一個新的 String 對象只是為了作為現有 String 的副本(無論它是文字還是另一個你已經擁有的 String 變量)更加冗長和效率低下。

事實上,Effective Java(我強烈推薦)正是使用它作為“避免創建不必要的對象”的示例之一:


作為不該做什么的極端示例,請考慮以下語句:

String s = new String("stringette");  **//DON'T DO THIS!**

(有效的 Java,第二版)

這里引用自 Effective Java 第三版(第 17 條:最小化可變性)一書:

不可變對象可以自由共享這一事實的結果是,您永遠不必制作它們的防御性副本(第 50 條)。 事實上,您根本不需要制作任何副本,因為副本將永遠等同於原件。 因此,您不需要也不應該在不可變類上提供克隆方法或復制構造函數(第 13 條)。 這在 Java 平台的早期並沒有得到很好的理解,所以 String 類確實有一個復制構造函數,但它應該很少使用,如果有的話,應該很少使用。

所以這是Java的一個錯誤決定,因為String類是不可變的,他們不應該為這個類提供復制構造函數,如果你想對不可變類進行昂貴的操作,你可以使用公共可變伴隨類,即StringBuilderStringBuffer String

在 Java 中有兩種方法可以創建字符串。 以下是這兩種方式的示例: 1) 聲明一個 String 類型的變量(Java 中的一個類)並將其分配給一個應該放在雙引號之間的值。 這將在內存的字符串池區域中創建一個字符串。 例如:String str = "JAVA";

2)使用String類的構造函數,傳入一個字符串(雙引號內)作為參數。 例如:String s = new String("JAVA"); 這將在主內存和字符串池中創建一個新字符串 JAVA,如果該字符串尚未出現在字符串池中。

通常,這表明有人對初始化時聲明的新式 C++ 風格不滿意。

在 C 時代,在內部范圍內定義自動變量被認為不是一種好形式; C++ 消除了解析器的限制,而 Java 對此進行了擴展。

所以你看到的代碼有

int q;
for(q=0;q<MAX;q++){
    String s;
    int ix;
    // other stuff
    s = new String("Hello, there!");
    // do something with s
}

在極端情況下,所有聲明可能都在函數的頂部,而不是像這里的for循環那樣在封閉的范圍內。

但是,一般來說,這樣做的效果是導致 String ctor 被調用一次,結果 String 被丟棄。 (避免這種情況的願望正是導致 Stroustrup 允許在代碼中的任何地方進行聲明的原因。)所以你是對的,它充其量是不必要的和糟糕的風格,而且可能實際上是糟糕的。

我想這將取決於您看到的代碼示例。

大多數時候在代碼示例中使用類構造函數“new String()”只是為了展示一個非常熟悉的java類,而不是創建一個新的類。

大多數時候你應該避免使用它。 不僅因為字符串字面量是固定的,而且主要是因為字符串是不可變的。 有兩個副本代表同一個對象是沒有意義的。

盡管 Ruggs 提到的文章“有趣”,但除非在非常特殊的情況下否則不應使用它,因為它可能造成的損害大於好處。 您將針對實現而不是規范進行編碼,並且相同的代碼不能在 JRockit、IBM VM 或其他中運行相同的代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM