Java中字符串的不变性


218

考虑以下示例。

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! 

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


str只是引用而不是对象本身
ahmednabil88 '18

Answers:


316

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

以下面的代码为例:

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

现在,没有什么1,我们可以做些什么来s1会影响价值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实际上,它可能的突变的字符串(和其他不可变的对象)。它需要反射,并且非常非常危险,除非您真的有兴趣破坏程序,否则永远不要使用它。


15
虚构方法+1,它说明了不可变对象与其他对象之间的差异。
Zappi

1
感谢gustafc提供了正确的示例和清晰的解释。...但是,您能否仅回答问题中已编辑的部分?这将使我的理解清楚。
Light_handle

17
我以前从未见过这样的答案。讨论了每个细节。
Michael'Maik'Ardan

+1这是一个想法,java中的不可变对象就像按值复制一样,您可以对一个String进行2个引用,但由于它们是不可变的,因此应将它们视为2个独立的String,并且使用其中的一个不会影响其他
Khaled.K 2014年

1
Java-您认为知道的越多,实际知道的就越少。
zeeshan

23

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

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

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

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

final String STR = "Hello";

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


但是在这种情况下,String对象是“ str”,它首先包含值“ Hello”,然后被分配了新值“ Help!”。您的确切含义是“包含字符串“ Hello”和“ Help!”的String对象无法更改其值,因此它们是不可变的。如果这是一个愚蠢的问题,请原谅我。但是四必须清除它...
Light_handle

2
您有没有尝试过使用C进行编程的所有方法?只需阅读指针上的入门知识,您就会完全理解coobird的答案。
Ryan Fernandes

瞧...这就是我要避免的...我知道你是一个很棒的程序员...我只是想在这里学习Java ...所以如果你可以正确回答我的问题,请回答。 。
Light_handle

您混淆了引用和对象- str不是“对象”,而是对对象的引用。如果String str = "Hello";紧随其后的是String anotherReference = str;您没有2个String对象,则您只有一个对象(文字“ Hello”)和2个对它的引用(stranotherReference)。
Nate

我没有编辑的权利,但是如果这样做,我将把coobirds第一句编辑为:“ str引用可以更改,但实际的String对象本身不能更改。”
j3App

10

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

你看过吗?是。好。

String str = new String();

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

例如在内存中创建:

str --- > ""

str  = "Hello";

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

例如在内存中创建:

str -+   ""
     +-> "Hello"

str = "Help!";

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

例如在内存中创建:

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

“”和“你好”是否会收集垃圾?
普拉宾·蒂姆西纳

@PrabinTimsina这确实应该是一个新问题。答案是:stackoverflow.com/questions/15324143/…–
迈克尔·劳埃德·李

9

让我们分成几个部分

String s1 = "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为最终类的原因,否则任何人都可以修改和更改string的值。希望这会有所帮助。


6

最初引用的字符串对象str未更改,您所做的只是使它str引用了一个新的字符串对象。


5

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


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!

4

尽管java试图忽略它,但str无非是一个指针。这意味着,当您第一次编写时str = "Hello";,您将创建一个str指向的对象。当您str通过写入进行重新分配时,只要有Java感觉,就会str = "Help!";创建一个新对象,并"Hello"回收旧对象。


3

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

变量str是对对象的引用,当您为str分配新值时,您没有更改其引用的对象的值,而是引用了另一个对象。


3

字符串类是不可变的,您不能更改不可变对象的值。但是在使用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)HelloWorld


2

用:

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

如果您在此处看到我使用的concat方法来更改原始字符串,即使用字符串“ Added String”更改“ New 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

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对象

0

对于那些想知道如何打破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

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


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

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

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

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

我希望这有帮助。


0

我可以说不变性是您不能更改String本身。假设您有字符串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

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

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


0

字符串是不可变的,这意味着您不能更改对象本身,但是可以更改对对象的引用。当您调用= =“ 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"

0

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

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


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)

  • 作者李·博因顿
  • 作者亚瑟·范·霍夫(Arthur van Hoff)
  • 作者马丁·布赫霍尔茨(Martin Buchholz)
  • 作者乌尔夫·齐比斯(Ulf Zibis)

资料来源:String的JavaDoc。


0

Object字符串-方法本身被设置为“不可变的”。此操作不会产生任何变化:“ 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的哈希码保持不变。


0

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

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

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

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

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


0

我会用一个简单的例子来解释


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


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

例如

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

因此s2将具有“ hel”;


-1

Java中Immutable和Final中的字符串仅表示无法更改或修改:

情况1:

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

输出:ABC

原因:对象引用str并未更改,实际上创建了一个新对象“ DEF”,该对象位于池中并且根本没有引用(即丢失)。

情况2:

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


-1

因为String是不可变的,所以如果您不将函数的返回值分配给字符串,则更改不会发生。因此在您的问题中,将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; } }

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.