Java 中字符串的不可变性

Immutability of Strings in Java

提问人:Light_handle 提问时间:10/12/2009 最后编辑:GnarkLight_handle 更新时间:8/31/2019 访问量:106189

问:

请看以下示例。

String str = new String();

str  = "Hello";
System.out.println(str);  //Prints Hello

str = "Help!";
System.out.println(str);  //Prints Help!

现在,在 Java 中,String 对象是不可变的。那么为什么可以为对象分配值“Help!”呢?这难道不是与 Java 中字符串的不变性相矛盾吗?谁能给我解释一下不变性的确切概念?str

编辑:

还行。我现在明白了,但只是一个后续问题。下面的代码呢?

String str = "Mississippi"; 
System.out.println(str); // prints Mississippi 

str = str.replace("i", "!"); 
System.out.println(str); // prints M!ss!ss!pp! 

这是否意味着再次创建两个对象(“Mississippi”和“M!ss!ss!pp!”),并且引用指向方法之后的另一个对象?strreplace()

Java 字符串 不可变性

评论

1赞 Ahmed Nabil 11/12/2018
str 只是一个引用,而不是对象本身
1赞 Pushkarraj Pujari 8/28/2019
我希望这对 pushkarrajpujari.com/article/strings-in-java 有所帮助

答:

26赞 coobird 10/12/2009 #1

引用的对象可以更改,但实际对象本身不能更改。strString

包含字符串的对象不能更改其值,因此它们是不可变的。String"Hello""Help!"

对象的不可变性并不意味着指向对象的引用不能更改。String

防止引用更改的一种方法是将其声明为:strfinal

final String STR = "Hello";

现在,尝试将另一个分配给将导致编译错误。StringSTR

评论

0赞 Light_handle 10/12/2009
但在本例中,String 对象是“str”,它首先包含值“Hello”,然后分配新值“Help!”。“包含字符串”Hello“和”Help!“的 String 对象不能更改其值,因此它们是不可变的,这到底是什么意思???如果这是一个愚蠢的问题,请原谅我。但是我必须清除它......
2赞 Ryan Fernandes 10/12/2009
你有没有尝试过用 C 语言编程?只需阅读指针上的入门书,您就会完全理解 coobird 的答案。
0赞 Light_handle 10/12/2009
看。。。这就是我想避免的......我知道你是一个伟大的程序员......我只是想在这里学习java......所以如果你能正确回答我的问题,那么请回答它......
0赞 Nate 10/12/2009
你混淆了引用和对象 - 不是“对象”,而是对对象的引用。如果后面没有 2 个 String 对象,则有一个对象(文本“Hello”)和 2 个对它的引用(和)。strString str = "Hello";String anotherReference = str;stranotherReference
0赞 j3App 11/22/2017
我没有编辑权,但如果我有,我会将 coobirds 第一句话编辑为:“str 引用可以更改,但实际的 String 对象本身不能。
6赞 daveb 10/12/2009 #2

首先引用的字符串对象没有更改,您所做的只是引用一个新的字符串对象。strstr

5赞 akf 10/12/2009 #3

字符串不会更改,对它的引用会更改。您将不可变性与字段的概念混淆了。如果字段被声明为 ,则一旦分配了该字段,则无法重新分配该字段。finalfinal

3赞 Preston Guillot 10/12/2009 #4

不可变性意味着实例化对象的值不能改变,你永远不能把“你好”变成“帮助!

变量 str 是对对象的引用,当您为 str 赋值时,您不是在更改它引用的对象的值,而是在引用不同的对象。

4赞 twolfe18 10/12/2009 #5

尽管 java 试图忽略它,但只不过是一个指针。这意味着,当您第一次编写 时,您将创建一个指向的对象。当您通过编写重新分配时,会创建一个新对象,并且只要 java 愿意,旧对象就会被垃圾回收。strstr = "Hello";strstrstr = "Help!";"Hello"

346赞 gustafc 10/12/2009 #6

str不是对象,而是对对象的引用。 和 是两个不同的对象。因此,指向一个字符串。你可以改变它所指向的内容,但不能改变它所指向的内容。"Hello""Help!"Stringstr

以这段代码为例:

String s1 = "Hello";
String s2 = s1;
// s1 and s2 now point at the same string - "Hello"

现在,我们无法对1 做任何事情来影响 的值。它们引用同一个对象 - 字符串 - 但该对象是不可变的,因此无法更改。s1s2"Hello"

如果我们做这样的事情:

s1 = "Help!";
System.out.println(s2); // still prints "Hello"

在这里,我们看到了改变对象和更改引用之间的区别。 仍然指向我们最初设置为指向的同一对象。设置为仅更改引用,而它最初引用的对象保持不变。s2s1s1"Help!"String

如果字符串可变的,我们可以做这样的事情:

String s1 = "Hello";
String s2 = s1;
s1.setCharAt(1, 'a'); // Fictional method that sets character at a given pos in string
System.out.println(s2); // Prints "Hallo"

编辑以响应 OP 的编辑:

如果你看一下String.replace(char,char)的源代码(也可以在JDK安装目录的src.zip中找到 - 一个专业提示是,每当你想知道某些东西是如何工作的时,都要看看那里),你可以看到它的作用是这样的:

  • 如果当前字符串中有一个或多个匹配项,请复制当前字符串,其中所有匹配项都替换为 。oldCharoldCharnewChar
  • 如果当前字符串中不存在,则返回当前字符串。oldChar

所以是的,创建一个新对象。同样,以下情况成立:"Mississippi".replace('i', '!')String

String s1 = "Mississippi";
String s2 = s1;
s1 = s1.replace('i', '!');
System.out.println(s1); // Prints "M!ss!ss!pp!"
System.out.println(s2); // Prints "Mississippi"
System.out.println(s1 == s2); // Prints "false" as s1 and s2 are two different objects

你现在的功课是看看如果你改成:)上面的代码有什么作用s1 = s1.replace('i', '!');s1 = s1.replace('Q', '!');


1 实际上,可以改变字符串(和其他不可变对象)。它需要反思,非常非常危险,除非你真的有兴趣破坏程序,否则永远不应该使用。

评论

16赞 Zappi 10/12/2009
+1 对于虚构方法,它展示了不可变对象与其他对象之间的差异。
1赞 Light_handle 10/13/2009
感谢 gustafc 的正确示例和清晰的解释......但是你能回答问题中的编辑部分吗?这将使我的理解更加清晰。
18赞 Michael 'Maik' Ardan 8/29/2013
我以前从未见过这样的答案。讨论了每一个细节。
0赞 Khaled.K 3/27/2014
+1 这里有一个想法,java 中的不可变对象就像按值复制一样,你可以对一个字符串有 2 个引用,但你应该将它们视为 2 个单独的字符串,因为它是不可变的,并且使用其中一个不会影响另一个
1赞 zeeshan 3/17/2017
Java——你认为你知道的越多,你实际知道的就越少。
10赞 Michael Lloyd Lee mlk 10/12/2009 #7

Light_handle 我建议你读一读《杯子大小》——一个关于变量按值传递的故事。在阅读上面的帖子时,这将有很大帮助。

你读过吗?是的。好。

String str = new String();

这将创建一个名为 “” 的新“远程控制”,并将其设置为值(或 )。strnew String()""

例如,在内存中,这会创建:

str --- > ""

str  = "Hello";

然后更改遥控器 “”,但不修改原始字符串。str""

例如,在内存中,这会创建:

str -+   ""
     +-> "Hello"

str = "Help!";

然后,这将更改遥控器 “”,但不会修改原始字符串或遥控器当前指向的对象。str""

例如,在内存中,这会创建:

str -+   ""
     |   "Hello"
     +-> "Help!"

评论

0赞 Prabin Timsina 2/10/2016
“”和“Hello”会收集垃圾吗?
0赞 Michael Lloyd Lee mlk 2/11/2016
@PrabinTimsina这确实应该是一个新问题。答案是:stackoverflow.com/questions/15324143/......
5赞 user85421 10/13/2009 #8

关于问题的替换部分,请尝试以下操作:

String str = "Mississippi"; 
System.out.println(str); //Prints Mississippi 

String other = str.replace("i", "!"); 
System.out.println(str); //still prints Mississippi 
System.out.println(other);  // prints M!ss!ss!pp!
2赞 martynas 4/24/2015 #9

对于那些想知道如何在 Java 中打破字符串不可变性的人......

法典

import java.lang.reflect.Field;

public class StringImmutability {
    public static void main(String[] args) {
        String str1 = "I am immutable";
        String str2 = str1;

        try {
            Class str1Class = str1.getClass();
            Field str1Field = str1Class.getDeclaredField("value");

            str1Field.setAccessible(true);
            char[] valueChars = (char[]) str1Field.get(str1);

            valueChars[5] = ' ';
            valueChars[6] = ' ';

            System.out.println(str1 == str2);
            System.out.println(str1);
            System.out.println(str2);           
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}

输出

true
I am   mutable
I am   mutable
0赞 Nayan Srivastava 8/31/2015 #10

在 Java 中,对象通常通过引用访问。在您的代码段中,str 是一个引用,它首先被分配给 “Hello”(一个自动创建的对象或从常量池中获取),然后您将另一个对象 “Help!” 分配给同一个引用。需要注意的一点是,引用是相同的,并且是修改的,但对象是不同的。在你的代码中,还有一件事,你访问了三个对象,

  1. 当您调用 new String() 时。
  2. 当您分配“hello”时。
  3. 当您分配“帮助!

调用 new String() 会创建一个新对象,即使它存在于字符串池中,因此通常不应使用它。要将从 new String () 创建的字符串放入字符串池中,您可以尝试该方法。intern()

我希望这会有所帮助。

0赞 DeathRs 1/5/2016 #11

字符串不可变的。这意味着我们只能更改引用


String a = "a";
System.out.println("String a is referencing to "+a); // Output: a

a.concat("b");
System.out.println("String a is referencing to "+a); // Output: a

a = a.concat("b");
System.out.println("String a has created a new reference and is now referencing to "+a); // Output: ab
0赞 Sumedha Khatter 2/6/2016 #12

我可以说不变性是你不能改变字符串本身。假设您有字符串 x,其值为“abc”。现在你不能改变字符串,也就是说,你不能改变“abc”中的任何字符。

如果必须更改 String 中的任何字符,可以使用字符数组并对其进行更改或使用 StringBuilder。

String x = "abc";
x = "pot";
x = x + "hj";
x = x.substring(3);
System.out.println(x);

char x1[] = x.toCharArray();
x1[0] = 's';
String y = new String(x1);
System.out.println(y);

输出:

hj
sj
2赞 Nihar nayak 2/6/2016 #13

用:

String s = new String("New String");
s.concat(" Added String");
System.out.println("String reference -----> "+s); // Output: String reference -----> New String

如果你在这里看到我使用该方法来更改原始字符串,即带有字符串“Added String”的“New String”,但我仍然得到了之前的输出,因此它证明您不能更改 String 类对象的引用,但是如果您通过 StringBuilder 类执行此操作,它将起作用。下面列出了它。concat

StringBuilder sb = new StringBuilder("New String");
sb.append(" Added String");
System.out.println("StringBuilder reference -----> "+sb);// Output: StringBuilder reference -----> New String Added String
9赞 Shailesh Sonare 5/18/2016 #14

让我们把它分成几个部分

String s1 = "hello";

此语句创建包含 hello 的字符串,并在内存中(即常量字符串池中)占用空间,并将其分配给引用对象 s1

String s2 = s1;

此语句将相同的字符串 hello 分配给新引用 s2

         __________
        |          |
s1 ---->|  hello   |<----- s2
        |__________| 

两个引用都指向同一个字符串,因此输出相同的值,如下所示。

out.println(s1);    // o/p: hello
out.println(s2);    // o/p: hello

尽管 String不可变的,但赋值是可能的,因此 s1 现在将引用新的值堆栈

s1 = "stack";    
         __________
        |          |
s1 ---->|  stack   |
        |__________|

但是指向 hellos2 对象呢,它会保持原样。

         __________
        |          |
s2 ---->|  hello   |
        |__________|

out.println(s1);    // o/p: stack
out.println(s2);    // o/p: hello

由于 String 是不可变的,因此 Java 虚拟机不允许我们通过其方法修改字符串 s1。它将在池中创建所有新的 String 对象,如下所示。

s1.concat(" overflow");

                 ___________________
                |                   |
s1.concat ----> |  stack overflow   |
                |___________________|

out.println(s1);    // o/p: stack
out.println(s2);    // o/p: hello
out.println(s1.concat); // o/p: stack overflow

请注意,如果 String 是可变的,则输出将是

out.println(s1);    // o/p: stack overflow

现在你可能会惊讶于为什么 String 有像 concat() 这样的方法来修改。以下片段将消除您的困惑。

s1 = s1.concat(" overflow");

在这里,我们将字符串的修改值分配回 s1 引用。

         ___________________
        |                   |
s1 ---->|  stack overflow   |
        |___________________|


out.println(s1);    // o/p: stack overflow
out.println(s2);    // o/p: hello

这就是为什么 Java 决定将 String 作为最终类的原因,否则任何人都可以修改和更改字符串的值。 希望这会有所帮助。

3赞 Nirav Chhatrola 6/23/2016 #15

String 类是不可变的,你不能更改不可变对象的值。 但是在 String 的情况下,如果您更改 string 的值,它将在字符串池中创建新字符串,并且您的字符串引用该值而不是旧值。所以这样一来,字符串是不可变的。 让我们以你为例,

String str = "Mississippi";  
System.out.println(str); // prints Mississippi 

它将创建一个字符串“Mississippi”并将其添加到字符串池中 所以现在 str 指向密西西比州。

str = str.replace("i", "!");  
System.out.println(str); // prints M!ss!ss!pp! 

但经过上述操作, 另一个字符串将被创建“M!ss!ss!pp!”,并将其添加到字符串池中。和 现在 str 指向的是 M!ss!ss!pp!,而不是密西西比州。

因此,通过这种方式,当您更改字符串对象的值时,它将创建另一个对象并将其添加到字符串池中。

让我们再举一个例子

String s1 = "Hello"; 
String s2 = "World"; 
String s = s1 + s2;

上面的三行将字符串的三个对象添加到字符串池中。
1) 你好
2) 世界 3) 你好世界

0赞 user1554868 8/22/2016 #16

或者你可以试试:

public class Tester
{
public static void main(String[] args)
{
 String str = "Mississippi"; 
 System.out.println(str); // prints Mississippi 
 System.out.println(str.hashCode());

 str = str.replace("i", "!"); 
 System.out.println(str); // prints M!ss!ss!pp! 
 System.out.println(str.hashCode());
 }
 }

这将显示哈希码是如何变化的。

2赞 etlapa 9/14/2016 #17

就像 Linus Tolvards 说的:

谈话很便宜。显示代码

看看这个:

public class Test{
    public static void main(String[] args){

        String a = "Mississippi";
        String b = "Mississippi";//String immutable property (same chars sequence), then same object

        String c = a.replace('i','I').replace('I','i');//This method creates a new String, then new object
        String d = b.replace('i','I').replace('I','i');//At this moment we have 3 String objects, a/b, c and d

        String e = a.replace('i','i');//If the arguments are the same, the object is not affected, then returns same object

        System.out.println( "a==b? " + (a==b) ); // Prints true, they are pointing to the same String object

        System.out.println( "a: " + a );
        System.out.println( "b: " + b );

        System.out.println( "c==d? " + (c==d) ); // Prints false, a new object was created on each one

        System.out.println( "c: " + c ); // Even the sequence of chars are the same, the object is different
        System.out.println( "d: " + d );

        System.out.println( "a==e? " + (a==e) ); // Same object, immutable property
    }
}

输出为

a==b? true
a: Mississippi
b: Mississippi
c==d? false
c: Mississippi
d: Mississippi
a==e? true

因此,请记住两件事:

  • 字符串是不可变的,直到你应用一个操作和创建一个新方法的方法(c&d案例)。
  • 如果两个参数相同,则 Replace 方法返回相同的 String 对象
-1赞 Aftab 9/23/2016 #18

Java 中的 Immutable 和 Final 中的字符串只是意味着它不能被更改或修改:

案例一:

class TestClass{  
 public static void main(String args[]){  
   String str = "ABC";  
   str.concat("DEF");  
   System.out.println(str);  
 }  
} 

输出:ABC

原因:对象引用 str 没有更改,实际上是一个新对象 “DEF”是在池中创建的,根本没有引用 (即丢失)。

案例二:

class TestClass{  
 public static void main(String args[]){  
   String str="ABC";  
   str=str.concat("DEF");  
   System.out.println(str);  
 }  
}  

输出:ABCDEF

原因:在本例中,str 现在引用一个新对象“ABCDEF” 因此它打印 ABCDEF,即以前的 str 对象“ABC”在池中丢失,没有引用。

0赞 e2rabi 6/30/2017 #19

字符串不可变意味着您不能更改对象本身,但可以更改对对象的引用。当您调用 a = “ty” 时,您实际上是在更改对 String 文本 “ty” 创建的新对象的引用。更改对象意味着使用其方法更改其字段之一(或者字段是公共的而不是最终的,因此可以从外部更新它们,而无需通过方法访问它们),例如:

Foo x = new Foo("the field");
x.setField("a new field");
System.out.println(x.getField()); // prints "a new field"

在不可变类中(声明为 final,以防止通过继承进行修改)(它的方法不能修改其字段,并且字段始终是私有的,建议是 final),例如 String,您不能更改当前的 String,但您可以返回一个新的 String,即:

String s = "some text";
s.substring(0,4);
System.out.println(s); // still printing "some text"
String a = s.substring(0,4);
System.out.println(a); // prints "some"
0赞 Afzal Ahmad 7/15/2017 #20

这里的不可变性意味着实例可以指向其他引用,但字符串的原始内容不会在原始引用处被修改。 让我用你给出的第一个例子来解释。 第一个 str 指向“你好”,它还可以。 第二次指向“救命! 在这里,str 开始指向“Help!”,并且“Hello”字符串的引用丢失了,我们无法将其恢复。

事实上,当 str 尝试修改现有内容时,将生成另一个新字符串,并且 str 将开始指向该引用。 因此,我们看到原始引用处的字符串没有被修改,但在其引用处是安全的,并且对象的实例开始指向不同的引用,因此不可变性是保守的。

0赞 Ajeet Ganga 7/17/2017 #21

回答得太晚了,但想把 Java 中 String 类的作者的简洁消息放进去

字符串是恒定的;它们的值在它们之后无法更改 创建。字符串缓冲区支持可变字符串。因为字符串 对象是不可变的,它们可以共享。

从本文档中可以得出,任何更改字符串的内容都会返回不同的对象(可能是新的或内部的和旧的)。 关于这一点的不那么微妙的提示应该来自函数签名。 想一想,“为什么他们让对象上的函数返回对象而不是状态?

public String replace(char oldChar, char newChar) 

还有一个使这种行为明确的来源(来自替换函数文档)

返回一个新字符串,该字符串由替换所有出现的 oldChar 与 newChar 一起。

来源: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#replace(char,%20char)

  • 作者 Lee Boynton
  • 作者 Arthur van Hoff
  • 作者 Martin Buchholz
  • 作者 Ulf Zibis

来源:字符串的 JavaDoc。

0赞 Loshen Naicker 10/3/2017 #22

Object string - methods 本身是“不可变的”。 此操作不会产生任何更改: “letters.replace(”bbb“, ”aaa“);”

但是,分配数据确实会导致对 Strings 内容的更改发生变化:

    letters = "aaa";
    letters=null;
    System.out.println(letters);
    System.out.println(oB.hashCode());
    System.out.println(letters);
    letters = "bbbaaa";
    System.out.println(oB.hashCode());
    System.out.println(letters);

字符串 Object 的哈希码不会更改。

-1赞 Shivam Shukla 11/29/2017 #23

因为 String 是不可变的,所以如果你不将函数的返回值分配给问题中的 string.so 将交换函数返回值的值分配给 s,则不会发生更改。

s=交换(s, n1, n2) ;然后字符串 s 的值将发生变化。

当我编写程序以获取一些排列字符串时,我也得到了不变的值(虽然它没有给出所有的排列,但这是为了回答你的问题)

下面是一个示例。

> import java.io.*;  public class MyString { public static void
> main(String []args)throws IOException {  BufferedReader br=new
> BufferedReader(new InputStreamReader(System.in));  String
> s=br.readLine().trim(); int n=0;int k=0;  while(n!=s.length()) {
> while(k<n){  swap(s,k,n); System.out.println(s); swap(s,k,n); k++; }
> n++; } }  public static void swap(String s,int n1,int n2) { char temp;
> temp=s.charAt(n1); StringBuilder sb=new StringBuilder(s);
> sb.setCharAt(n1,s.charAt(n2)); sb.setCharAt(n2,temp); s=sb.toString();
> } }

但是我没有从上面的代码中获取字符串的置换值。所以我将交换函数的返回值分配给字符串并得到了字符串的更改值。赋值后,我得到了字符串的置换值。

/import java.util.*; import java.io.*; public class MyString { public static void main(String []args)throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); 
String s=br.readLine().trim(); int n=0;int k=0; 
while(n!=s.length()){ while(k<n){ s=swap(s,k,n); 
System.out.println(s); s=swap(s,k,n); k++; } n++; } } 
public static String swap(String s,int n1,int n2){
char temp; temp=s.charAt(n1); StringBuilder sb=new StringBuilder(s); sb.setCharAt(n1,s.charAt(n2)); sb.setCharAt(n2,temp); s=sb.toString(); return s; } }
0赞 Uddhav P. Gautam 7/21/2018 #24

如果是你的字符串,则不能更改为 .此属性称为不可变性属性。HELLOHELLOHILLO

您可以使用多个指针 String 变量来指向 HELLO String。

但是如果 HELLO 是 char Array,那么您可以将 HELLO 更改为 HILLO。例如,

char[] charArr = 'HELLO';
char[1] = 'I'; //you can do this

编程语言具有不可变的数据变量,因此它可以用作键、值对中的键。

-1赞 Arasn 4/29/2019 #25
    public final class String_Test {

    String name;
    List<String> list=new ArrayList<String>();

    public static void main(String[] args) {

        String_Test obj=new String_Test();
        obj.list.add("item");//List will point to a memory unit- i.e will have one Hashcode value #1234

        List<String> list2=obj.list; //lis1 also will point to same #1234

        obj.list.add("new item");//Hashcode of list is not altered- List is mutable, so reference remains same, only value in that memory location changes

        String name2=obj.name="Myname"; // name2 and name will point to same instance of string -Hashcode #5678
        obj.name = "second name";// String is Immutable- New String HAI is created and name will point to this new instance- bcoz of this Hashcode changes here #0089

        System.out.println(obj.list.hashCode());
        System.out.println(list2.hashCode());
        System.out.println(list3.hashCode());

        System.out.println("===========");
        System.out.println(obj.name.hashCode());
        System.out.println(name2.hashCode());
    }
}

会生产出来放这样的东西

1419358369 1419358369

103056 65078777

不可变对象的用途是其值一旦分配就不应更改。 每次您尝试根据实现更改它时,它都会返回新对象。 注意:可以使用 Stringbuffer 而不是 string 来避免这种情况。

对于你的最后一个问题::你将在字符串池中有一个引用和2个字符串。 除了引用将指向 m!ss!ss!pp!

0赞 Aadesh Nirfarake 8/31/2019 #26

我会用简单的例子来解释它


考虑任何字符数组:例如char a[]={'h','e','l','l','o'}; 和一个字符串: 字符串 s=“hello”;


在字符数组上,我们可以执行操作,例如使用迭代数组仅打印最后三个字母; 但是在字符串中,我们必须创建新的 String 对象并复制所需的子字符串,其地址将在新的字符串对象中。

例如

***String s="hello";
String s2=s.substrig(0,3);***

所以 S2 会有“hel”;