Java中可变字符串和不可变字符串有什么区别?

58

据我所知,可变字符串可以被改变,而不可变字符串则不能。

在这里,我想像这样改变字符串的值,

String str="Good";
str=str+" Morning";

另一种方法是,

StringBuffer str= new StringBuffer("Good");
str.append(" Morning");
在这两种情况下,我都试图改变str的值。有人能告诉我,在这两种情况下有什么区别,并给我解释可变和不可变对象的清晰图像吗?

2
在Java中,字符串是不可变的。没有可变字符串。 - Suresh Atta
2
可能是字符串是不可变的。这到底意味着什么?的重复问题。 - dharam
1
@Raghu- 不是的。StringBuffer 对象不是可变字符串。一旦创建了一个字符串,它就永远是不可变的。 - TheLostMind
1
@TheLostMind 是的,我明白了。我并不是在说 StringBuffer 是可变的字符串,而是在说 StringBuffer 是可变的对象。现在我是正确的吗? - Raghu
这个链接提供了一个详细的解释:http://www.javaranch.com/journal/2003/04/immutable.htm - Karthik
显示剩余2条评论
10个回答

106

案例1:

String str = "Good";
str = str + " Morning";

在上面的代码中,你创建了3个String对象。

  1. "Good"进入了字符串池
  2. " Morning"也进入了字符串池
  3. "Good Morning"是通过连接"Good"和" Morning"创建的。这个字符串被放在堆内存中。

注意:String永远是不可变的。没有可变的String这样的东西。 str只是一个指向"Good Morning"的引用。实际上,你并不是在处理1个对象。你有3个不同的String对象。


情况2:

StringBuffer str = new StringBuffer("Good"); 
str.append(" Morning");

StringBuffer 包含字符数组。它与 String 不同,不是 同一种类型。 上述代码将字符添加到现有数组中。实际上,StringBuffer 是可变的,但它的 String 表示形式不是。


2
请问为什么“GoodMorning”会进入堆而不是字符串池? - DilanG
2
@DilanG - 在内部(如果字符串文字没有标记为“final”),字符串连接使用StringBuilder/ StringBuffer进行。 - TheLostMind
1
在第二种情况下,str.append(" Morining")," Morning" 存储在字符串池中吗? - perfectus

17
什么是Java中可变和不可变字符串的区别?
不可变字符串存在,可变字符串不存在。

3
StringBuffer扮演了可变字符串的角色。请注意String和仅仅的“string”之间的区别。 - William F. Jameson
1
@WilliamF.Jameson - 不,StringBuffer并不扮演可变字符串的角色。它是一个可以转换为字符串的数组。一旦您在其上调用toString(),然后添加字符并再次调用toString(),您将拥有2个不同的字符串对象。 - TheLostMind
4
请仔细阅读我的评论:string 不等同于 String。前者是一个通用概念,后者是 Java 类。在 Java 中,StringBuffer 绝对可以用作可变字符串的目的。 - William F. Jameson

11
在Java中,所有字符串都是不可变的。当你尝试修改一个String时,实际上是创建一个新的字符串。但是,当你使用StringBuilder时,你实际上是修改了内容,而不是创建一个新的字符串。

7

Java中的String是不可变的。

在您的第一个示例中,您正在更改对String引用,从而将其赋值为两个其他Strings组合而成的值:str +“ Morning”

相反,StringBuilderStringBuffer可以通过其方法进行修改。


4

在 Java 中,String不可变的。然而,在编程上下文中,“可变”是什么意思呢?请考虑以下类:

public class Dimension {
    private int height;

    private int width;

    public Dimenstion() {
    }

    public void setSize(int height, int width) {
        this.height = height;
        this.width = width;
    }

    public getHeight() {
        return height;
    }

    public getWidth() {
        return width;
    }
}

现在,创建了 Dimension 的实例后,我们总是可以更新其属性。请注意,如果类的实例的任何属性(或者换一种说法,状态)可以被更新,那么它就被称为可变的。我们总是可以进行以下操作:
Dimension d = new Dimension();
d.setSize(10, 20);// Dimension changed
d.setSize(10, 200);// Dimension changed
d.setSize(100, 200);// Dimension changed

让我们来看看Java中创建字符串的不同方法。

String str1 = "Hey!";
String str2 = "Jack";
String str3 = new String("Hey Jack!");
String str4 = new String(new char[] {'H', 'e', 'y', '!'});
String str5 = str1 + str2;
str1 = "Hi !";
// ...

因此,
  1. str1str2是字符串字面量,在字符串常量池中创建。
  2. str3str4str5是放置在堆内存中的字符串对象。
  3. str1 = "Hi!";在字符串常量池中创建了"Hi!",与str1之前引用的"Hey!"完全不同。

在这里,我们创建了字符串字面量或字符串对象。它们是不同的,我建议您阅读以下文章以了解更多信息。

在任何字符串声明中,有一件事是共同的,它不会被修改,但却被创建或转移到其他位置。

String str = "Good"; // Create the String literal in String pool
str = str + " Morning"; // Create String with concatenation of str + "Morning"
|_____________________|
       |- Step 1 : Concatenate "Good"  and " Morning" with StringBuilder
       |- Step 2 : assign reference of created "Good Morning" String Object to str

字符串为什么是不可变的?

这是一种不变的行为,意思是一旦赋值,就无法以任何其他方式更新该值。String类在内部使用字符数组来保存数据。此外,该类被创建为不可变。看看如何定义不可变类的策略。

移动引用并不意味着你改变了它的值。如果您可以更新String类中背后的字符数组,则该类将是可变的。但实际上,数组将被初始化一次,并在整个程序中保持不变。

为什么StringBuffer是可变的?

正如您已经猜到的,StringBuffer类本身是可变的,因为您可以直接更新其状态。与String类类似,它也将值保存在字符数组中,并且您可以通过不同方法(例如append,delete,insert等)操纵该数组,从而直接更改字符值数组。


3
当你说str时,你需要小心你的意思:
  • 你是指变量str吗?
  • 还是你是指由str引用的对象
在你的StringBuffer示例中,你没有改变str的值,在你的String示例中,你没有改变String对象的状态。
最具煽动性的体验方式可能是这样的:
static void change(String in) { 
  in = in + " changed";
}

static void change(StringBuffer in) {
  in.append(" changed");
}

public static void main(String[] args) {
   StringBuffer sb = new StringBuffer("value");
   String str = "value";
   change(sb);
   change(str);
   System.out.println("StringBuffer: "+sb);
   System.out.println("String: "+str);
}

2
在Java中,所有的字符串都是不可变的。当你试图修改一个字符串时,实际上是创建了一个新的字符串。
以下是创建字符串对象的方式:
  1. Using String literal

    String str="java";
    
  2. Using new keyword

    String str = new String("java");
    
  3. Using character array

    char[] helloArray = { 'h', 'e', 'l', 'l', 'o', '.' };
    
    String helloString = new String(helloArray);   
    
关于字符串的不可变性,简单来说就是不能被修改或改变。
举个例子,
我正在将值初始化为字符串字面量s。
String s="kumar";

以下我将使用hashcode()显示位置地址的十进制表示。
System.out.println(s.hashCode());

简单地输出一个字符串s的值。
System.out.println("value "+s);

好的,这次我将"Kumar"的值初始化为s1。
String s1="kumar";   // what you think is this line, takes new location in the memory ??? 

好的,让我们通过显示我们创建的s1对象的哈希码来检查。
System.out.println(s1.hashCode());

好的,让我们检查下面的代码。
String s2=new String("Kumar");
    System.out.println(s2.hashCode());  // why this gives the different address ??

好的,请检查下面的代码。
String s3=new String("KUMAR");
    System.out.println(s3.hashCode());  // again different address ???

是的,如果您看到字符串's'和's1'具有相同的哈希码,因为's'和's1'持有的值相同,即'kumar'。
让我们考虑字符串's2'和's3',这两个字符串的哈希码在某种意义上不同,因为它们存储在不同的位置,因为您可以看到它们的值是不同的。
因为's'和's1'的哈希码相同,所以它们的值相同并且存储在相同的位置。
示例1: 尝试下面的代码并逐行分析。
public class StringImmutable {
public static void main(String[] args) {

    String s="java";
    System.out.println(s.hashCode());
    String s1="javA";
    System.out.println(s1.hashCode());
    String s2=new String("Java");
    System.out.println(s2.hashCode());
    String s3=new String("JAVA");
    System.out.println(s3.hashCode());
}
}

例子2:尝试以下代码并逐行分析
public class StringImmutable {
    public static void main(String[] args) {

        String s="java";
        s.concat(" programming");  // s can not be changed "immutablity"
        System.out.println("value of s "+s);
        System.out.println(" hashcode of s "+s.hashCode());

        String s1="java";
        String s2=s.concat(" programming");   // s1 can not be changed "immutablity" rather creates object s2
        System.out.println("value of s1 "+s1);
        System.out.println(" hashcode of s1 "+s1.hashCode());  

        System.out.println("value of s2 "+s2);
        System.out.println(" hashcode of s2 "+s2.hashCode());

    }
}

好的,让我们看看可变和不可变之间的区别。
可变(它可以改变) vs. 不可变(它无法改变)
public class StringMutableANDimmutable {
    public static void main(String[] args) {


        // it demonstrates immutable concept
        String s="java";
        s.concat(" programming");  // s can not be changed (immutablity)
        System.out.println("value of s ==  "+s); 
        System.out.println(" hashcode of s == "+s.hashCode()+"\n\n");


        // it demonstrates mutable concept
        StringBuffer s1= new StringBuffer("java");
        s1.append(" programming");  // s can be changed (mutablity)
        System.out.println("value of s1 ==  "+s1); 
        System.out.println(" hashcode of s1 == "+s1.hashCode());


    }
}

任何进一步的问题吗?请写下来...

请不要使用StringBuffer,因为它在2004年被StringBuilder所取代。 - Peter Lawrey
@PeterLawrey 谢谢,我刚刚展示了。 - k_kumar
顺便说一句,hashCode()并不是“显示位置地址的十进制表示”,它只是基于字符串内容的计算,完全不适合确定两个实例是否相同("Aa".hashCode() == "BB".hashCode() == 2112,但"Aa"永远不会与"BB"处于相同的位置)。 - undefined

0

我修改了William的代码,并添加了输出注释以便更好地理解

   static void changeStr(String in) { 
      in = in+" changed";
      System.out.println("fun:"+in); //value changed 
    }
    static void changeStrBuf(StringBuffer in) {
      in.append(" changed");   //value changed
    }

    public static void main(String[] args) {
       StringBuffer sb = new StringBuffer("value");
       String str = "value";
       changeStrBuf(sb);
       changeStr(str);
       System.out.println("StringBuffer: "+sb); //value changed
       System.out.println("String: "+str);       // value 
    }

在上面的代码中,看一下main()和changeStr()中str的值,即使你在changeStr()中改变了str的值,它只会影响到该函数,但在主函数中该值并没有改变,但StringBuffer不是这种情况。
在StringBuffer中,改变的值会作为全局变量受到影响。
因此,String是不可变的,而StringBuffer是可变的...
简单来说,无论你对String对象做了什么更改,都只会影响到该函数,而不会被改变。

0
可变意味着您将保存相同的引用到变量,并更改其内容,但不可变意味着您不能更改内容,而是会声明一个新的引用,其中包含变量的新值和旧值。
例如: 不可变 -> String
String x = "value0ne"; // 地址 one x += "valueTwo"; // 另一个地址 {地址 two} 堆内存上的地址发生了变化。
可变 -> StringBuffer - StringBuilder StringBuilder sb = new StringBuilder(); sb.append("valueOne"); // 地址 One sb.append("valueTwo"); // 地址 One sb仍然在相同的地址上,希望这个注释有所帮助。

0
可变变量是指其值可以在原地更改的变量,而不可变变量的值不会在原地更改。修改不可变变量将重新构建相同的变量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接