Java中字符串的不变性

时间:2009-10-12 01:23:21

标签: java string immutability

请考虑以下示例。

String str = new String();

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

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

现在,在Java中,String对象是不可变的。那么为什么可以为对象str赋值“帮助!”。这与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!”)并且引用strreplace()方法之后指向另一个对象?

26 个答案:

答案 0 :(得分:286)

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

获取此代码,例如:

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

现在,我们可以对s1做任何 1 ,这会影响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的编辑:

如果你看一下source code for 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 实际上, 可以改变字符串(以及其他不可变对象)。它需要反思并且非常非常危险,除非你真的对破坏程序感兴趣,否则永远不要使用它。

答案 1 :(得分:23)

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

包含字符串String"Hello"的{​​{1}}个对象无法更改其值,因此它们是不可变的。

"Help!"个对象的不变性并不意味着指向该对象的引用不能改变。

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

final

现在,尝试将另一个final String STR = "Hello"; 分配给String将导致编译错误。

答案 2 :(得分:9)

Light_handle我建议您阅读Cup Size -- a story about variablesPass-by-Value Please (Cup Size continued)。阅读上述帖子时,这将有很大帮助。

你读过它们吗?是。好。

String str = new String();

这会创建一个名为“str”的新“遥控器”,并将其设置为值new String()(或"")。

e.g。在内存中创建:

str --- > ""

str  = "Hello";

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

e.g。在内存中创建:

str -+   ""
     +-> "Hello"

str = "Help!";

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

e.g。在内存中创建:

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

答案 3 :(得分:7)

让我们把它分成几个部分

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

虽然字符串 不可变,但可以进行分配,因此 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作为最终类的原因,否则任何人都可以修改和更改string的值。 希望这会有所帮助。

答案 4 :(得分:6)

str首次引用的字符串对象未被更改,您所做的只是make str引用新的字符串对象。

答案 5 :(得分: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!

答案 6 :(得分:5)

String不会改变,对它的引用会。您将不可变性与final字段的概念混为一谈。如果某个字段被声明为final,则一旦分配该字段,就无法重新分配。

答案 7 :(得分:3)

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

变量str是对象的引用,当你为str指定一个新值时,你没有改变它引用的对象的值,你引用的是另一个对象。

答案 8 :(得分:3)

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

答案 9 :(得分:2)

使用:

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

如果你看到这里,我使用concat方法来改变原始字符串,即&#34; New String&#34;用字符串&#34;添加了字符串&#34;,但我仍然得到了前面的输出,因此它证明你不能改变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

答案 10 :(得分:2)

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

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

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

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

但经过上述操作后, 将创建另一个字符串&#34; M!ss!ss!pp!&#34; 它将被添加到String池中。和 现在str指向M!ss!ss!pp !,而不是Mississippi。

所以通过这种方式当你改变字符串对象的值时,它将再创建一个对象并将其添加到字符串池中。

让我们再举一个例子

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

以上三行将向字符串池中添加三个字符串对象 1)你好 2)世界
3)HelloWorld

答案 11 :(得分: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&amp; d case)。
  • 如果两个参数相同,则方法返回相同的String对象

答案 12 :(得分:0)

我将用一个简单的示例进行解释


  

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


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

例如

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

因此s2将带有“ hel”;

答案 13 :(得分:0)

如果HELLO是您的字符串,则不能将HELLO更改为HILLO。此属性称为不变性属性。

您可以具有多个指针String变量来指向HELLO String。

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

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

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

答案 14 :(得分:0)

Object string - 方法本身是“不可变的”。 此操作不会产生任何更改:“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的哈希码不会改变。

答案 15 :(得分:0)

答案已经很晚了,但是希望在Java

中添加来自String类作者的简明信息
  

字符串是不变的;他们的价值观无法改变   创建。字符串缓冲区支持可变字符串。因为String   对象是不可变的,可以共享。

可以从此文档中获得任何更改字符串的内容,返回不同的对象(可以是新的或实习的和旧的)。 关于这个的不那么微妙的提示应该来自函数签名。 想想看,'为什么他们在一个对象上创建一个函数而不是状态?'。

STORED AS TEXTFILE

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

  

返回替换所有出现的新字符串   带有newChar的字符串中的oldChar。

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

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

来源:String的JavaDoc。

答案 16 :(得分:0)

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

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

答案 17 :(得分:0)

String是不可变的意味着您无法更改对象本身,但您可以更改对象的引用。当你调用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 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"

答案 18 :(得分: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());
 }
 }

这将显示哈希码如何更改。

答案 19 :(得分:0)

不变性我可以说是你无法改变String本身。假设你有String 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

答案 20 :(得分: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

答案 21 :(得分:0)

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

  1. 当你调用new String()时。
  2. 当你分配“你好”时。
  3. 当您指定“帮助!”时。
  4. 调用new String()即使它存在于字符串池中也会创建一个新对象,因此通常不应该使用它。要将从新String()创建的字符串放入字符串池,您可以尝试intern()方法。

    我希望这会有所帮助。

答案 22 :(得分:0)

对于那些想知道如何在Java中破坏String不变性的人......

<强>代码

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

答案 23 :(得分:-1)

Java中的String in Immutable和Final只是意味着它无法更改或修改:

案例1:

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

输出:ABC

     

原因:对象引用str实际上并未更改为新对象   &#34; DEF&#34;已创建,它在池中,根本没有引用   (即丢失)。

案例2:

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

输出:ABCDEF

     

原因:在这种情况下,str现在引用一个新对象&#34; ABCDEF&#34;   因此它打印ABCDEF,即先前的str对象&#34; ABC&#34;在没有参考的游泳池中丢失。

答案 24 :(得分:-1)

因为String是不可变的,所以如果你不将函数的返回值赋值给string.so,你的问题会将swap函数返回值赋值给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; } }

答案 25 :(得分:-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!