考虑下面的例子。

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


当前回答

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

当你调用new String()。 当你分配"hello"的时候。 当你说“救命!”

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

我希望这能有所帮助。

其他回答

STR不是一个对象,而是一个对象的引用。“Hello”和“Help!”是两个不同的String对象。因此,str指向一个字符串。你可以改变它指向的东西,但不能改变它指向的东西。

以下面的代码为例:

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

现在,我们对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在当前字符串中不存在,则返回当前字符串。

所以,是的,“密西西比”。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,上面的代码会做什么。替换(“我”、“!”);到s1 = s1。替换(“问”、“!”);:)


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

或者你可以试试:

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

这将显示hashcode是如何变化的。

因为String是不可变的,所以如果你不把function的返回值赋给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();
> } }

但是我没有从上面的代码得到字符串的排列值。因此,我将swap函数的返回值分配给字符串,并得到了更改的字符串值。在分配返回值后,我得到了字符串的排列值。

/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; } }

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

变量str是一个对象的引用,当你给str赋一个新值时,你没有改变它引用的对象的值,你引用的是一个不同的对象。

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

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

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

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

final String STR = "Hello";

现在,尝试将另一个String赋值给STR将导致编译错误。