230

考虑以下示例。

String str = new String();

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

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

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

编辑:

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

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!”),并且引用在方法str之后指向不同的对象replace()

4

26 回答 26

336

str不是对象,它是对对象的引用。"Hello"并且"Help!"是两个不同的String对象。因此,str 指向一个字符串。您可以更改它所指向的内容,但不能更改它所指向的内容。

以这段代码为例:

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

现在,我们无能为力1s1影响 的值s2。它们引用同一个对象 - 字符串"Hello"- 但该对象是不可变的,因此无法更改。

如果我们这样做:

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

在这里,我们看到了改变对象和更改引用之间的区别。仍然指向与我们最初设置指向s2的对象相同的对象。s1设置s1"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 中找到 - 一个专业提示是,只要您想知道某些东西是如何工作的,就可以查看那里),您会看到什么它确实如下:

  • 如果oldChar当前字符串中出现一个或多个 ,则制作当前字符串的副本,其中所有出现的oldChar都替换为newChar
  • 如果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实际上,可以改变字符串(和其他不可变对象)。它需要反射,非常非常危险,除非你真的有兴趣破坏程序,否则永远不要使用它。

于 2009-10-12T07:04:59.453 回答
25

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

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

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

可以防止str引用更改的一种方法是将其声明为final

final String STR = "Hello";

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

于 2009-10-12T01:24:27.847 回答
10

Light_handle 我建议您阅读Cup Size - 关于变量和Pass-by-Value Please的故事(Cup Size 续) 。阅读上面的帖子时,这将有很大帮助。

你读过它们吗?是的。好的。

String str = new String();

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

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

str --- > ""

str  = "Hello";

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

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

str -+   ""
     +-> "Hello"

str = "Help!";

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

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

str -+   ""
     |   "Hello"
     +-> "Help!"
于 2009-10-12T10:48:16.250 回答
9

让我们把它分成几个部分

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   |
        |__________|

但是指向hello的s2对象会保持原样。

         __________
        |          |
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 是最终类的原因否则任何人都可以修改和更改字符串的值。希望这会有所帮助。

于 2016-05-18T12:26:37.193 回答
6

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

于 2009-10-12T01:25:23.807 回答
5

字符串不会改变,对它的引用会。您将不变性与final字段的概念混淆了。如果一个字段被声明为final,一旦它被赋值,它就不能被重新赋值。

于 2009-10-12T01:26:09.217 回答
5

关于问题的替换部分,试试这个:

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!
于 2009-10-12T19:48:58.923 回答
4

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

于 2009-10-12T01:28:50.697 回答
3

不变性意味着实例化对象的值不能改变,你永远不能将“Hello”变成“Help!”。

变量 str 是对对象的引用,当您为 str 分配新值时,您并没有更改它引用的对象的值,而是引用了不同的对象。

于 2009-10-12T01:28:45.280 回答
3

String 类是不可变的,你不能改变不可变对象的值。但是在字符串的情况下,如果您更改字符串的值,它将在字符串池中创建新字符串,而不是您对该值的字符串引用而不是旧字符串。所以通过这种方式字符串是不可变的。举个例子,

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

它将创建一个字符串“Mississippi”并将其添加到字符串池中,因此现在 str 指向 Mississippi。

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)你好世界

于 2016-06-23T12:15:35.953 回答
2

采用:

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

如果你看到这里我使用了concat改变原始字符串的方法,即“New String”和一个字符串“Added String”,但我仍然得到了之前的输出,因此证明你不能改变对象的引用String 类的,但是如果你通过 StringBuilder 类来做这件事,它会起作用。它在下面列出。

StringBuilder sb = new StringBuilder("New String");
sb.append(" Added String");
System.out.println("StringBuilder reference -----> "+sb);// Output: StringBuilder reference -----> New String Added String
于 2016-02-06T12:06:10.917 回答
2

就像 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 对象
于 2016-09-14T03:46:52.613 回答
1

对于那些想知道如何在 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
于 2015-04-24T11:12:31.667 回答
0

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

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

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

我希望这有帮助。

于 2015-08-31T14:48:55.700 回答
0

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


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
于 2016-01-05T09:23:54.367 回答
0

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

如果您必须更改字符串中的任何字符,您可以使用字符数组并对其进行变异或使用 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
于 2016-02-05T21:34:10.957 回答
0

或者您可以尝试:

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());
 }
 }

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

于 2016-08-22T13:33:44.643 回答
0

字符串是不可变的意味着你不能改变对象本身,但是你可以改变对对象的引用。当您调用 a = "ty" 时,实际上是将 a 的引用更改为由字符串文字 "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 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"
于 2017-06-29T22:17:18.897 回答
0

这里的不变性意味着实例可以指向其他引用,但字符串的原始内容不会在原始引用处被修改。让我通过你给出的第一个例子来解释。第一个 str 指向“Hello”,到此为止。第二次指向“帮助!”。这里 str 开始指向“帮助!” 并且“Hello”字符串的引用丢失了,我们无法找回它。

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

于 2017-07-14T18:14:32.137 回答
0

超级迟到的答案,但想从 Java 中 String 类的作者那里得到一条简明的信息

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

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

public String replace(char oldChar, char newChar) 

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

返回一个新字符串,该字符串是用 newChar 替换此字符串中所有出现的 oldChar 所产生的。

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

  • 作者李博因顿
  • 作者亚瑟·范霍夫
  • 作者马丁·布赫霍尔兹
  • 作者乌尔夫·齐比斯

来源:String 的 JavaDoc。

于 2017-07-16T23:35:14.637 回答
0

对象字符串 - 方法本身是“不可变的”。此操作不会产生任何更改:“letters.replace("bbb", "aaa");"

但是分配数据确实会导致字符串内容发生变化:

    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的hashcode不变。

于 2017-10-03T13:20:00.927 回答
0

如果HELLO是您的 String 那么您不能更改HELLOHILLO. 此属性称为不变性属性。

您可以有多个指针字符串变量来指向 HELLO 字符串。

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

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

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

于 2018-07-20T20:53:26.673 回答
0

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


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


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

例如

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

所以 s2 将有“hel”;

于 2019-08-31T04:49:52.037 回答
-1

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

情况1:

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”在池中丢失,没有引用。

于 2016-09-23T06:35:22.987 回答
-1

因为字符串是不可变的,所以如果您不将函数的返回值分配给字符串,则不会发生更改。所以在您的问题中,将交换函数返回值的值分配给 s。

s=swap(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; } }
于 2017-11-29T13:31:09.457 回答
-1
    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!

于 2019-04-29T08:45:01.773 回答