提问人:Light_handle 提问时间:10/12/2009 最后编辑:GnarkLight_handle 更新时间:8/31/2019 访问量:106189
Java 中字符串的不可变性
Immutability of Strings in Java
问:
请看以下示例。
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!”),并且引用指向方法之后的另一个对象?str
replace()
答:
引用的对象可以更改,但实际对象本身不能更改。str
String
包含字符串的对象不能更改其值,因此它们是不可变的。String
"Hello"
"Help!"
对象的不可变性并不意味着指向对象的引用不能更改。String
防止引用更改的一种方法是将其声明为:str
final
final String STR = "Hello";
现在,尝试将另一个分配给将导致编译错误。String
STR
评论
str
String str = "Hello";
String anotherReference = str;
str
anotherReference
首先引用的字符串对象没有更改,您所做的只是引用一个新的字符串对象。str
str
字符串不会更改,对它的引用会更改。您将不可变性与字段的概念混淆了。如果字段被声明为 ,则一旦分配了该字段,则无法重新分配该字段。final
final
不可变性意味着实例化对象的值不能改变,你永远不能把“你好”变成“帮助!
变量 str 是对对象的引用,当您为 str 赋值时,您不是在更改它引用的对象的值,而是在引用不同的对象。
尽管 java 试图忽略它,但只不过是一个指针。这意味着,当您第一次编写 时,您将创建一个指向的对象。当您通过编写重新分配时,会创建一个新对象,并且只要 java 愿意,旧对象就会被垃圾回收。str
str = "Hello";
str
str
str = "Help!";
"Hello"
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 实际上,可以改变字符串(和其他不可变对象)。它需要反思,非常非常危险,除非你真的有兴趣破坏程序,否则永远不应该使用。
评论
Light_handle 我建议你读一读《杯子大小》——一个关于变量和按值传递的故事。在阅读上面的帖子时,这将有很大帮助。
你读过吗?是的。好。
String str = new String();
这将创建一个名为 “” 的新“远程控制”,并将其设置为值(或 )。str
new String()
""
例如,在内存中,这会创建:
str --- > ""
str = "Hello";
然后更改遥控器 “”,但不修改原始字符串。str
""
例如,在内存中,这会创建:
str -+ ""
+-> "Hello"
str = "Help!";
然后,这将更改遥控器 “”,但不会修改原始字符串或遥控器当前指向的对象。str
""
例如,在内存中,这会创建:
str -+ ""
| "Hello"
+-> "Help!"
评论
关于问题的替换部分,请尝试以下操作:
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!
对于那些想知道如何在 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
在 Java 中,对象通常通过引用访问。在您的代码段中,str 是一个引用,它首先被分配给 “Hello”(一个自动创建的对象或从常量池中获取),然后您将另一个对象 “Help!” 分配给同一个引用。需要注意的一点是,引用是相同的,并且是修改的,但对象是不同的。在你的代码中,还有一件事,你访问了三个对象,
- 当您调用 new String() 时。
- 当您分配“hello”时。
- 当您分配“帮助!
调用 new String() 会创建一个新对象,即使它存在于字符串池中,因此通常不应使用它。要将从 new String () 创建的字符串放入字符串池中,您可以尝试该方法。intern()
我希望这会有所帮助。
字符串是不可变的。这意味着我们只能更改引用。
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
我可以说不变性是你不能改变字符串本身。假设您有字符串 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
用:
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
让我们把它分成几个部分
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 作为最终类的原因,否则任何人都可以修改和更改字符串的值。 希望这会有所帮助。
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) 你好世界
或者你可以试试:
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());
}
}
这将显示哈希码是如何变化的。
就像 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 对象
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”在池中丢失,没有引用。
字符串不可变意味着您不能更改对象本身,但可以更改对对象的引用。当您调用 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"
这里的不可变性意味着实例可以指向其他引用,但字符串的原始内容不会在原始引用处被修改。 让我用你给出的第一个例子来解释。 第一个 str 指向“你好”,它还可以。 第二次指向“救命! 在这里,str 开始指向“Help!”,并且“Hello”字符串的引用丢失了,我们无法将其恢复。
事实上,当 str 尝试修改现有内容时,将生成另一个新字符串,并且 str 将开始指向该引用。 因此,我们看到原始引用处的字符串没有被修改,但在其引用处是安全的,并且对象的实例开始指向不同的引用,因此不可变性是保守的。
回答得太晚了,但想把 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。
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 的哈希码不会更改。
因为 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; } }
如果是你的字符串,则不能更改为 .此属性称为不可变性属性。HELLO
HELLO
HILLO
您可以使用多个指针 String 变量来指向 HELLO String。
但是如果 HELLO 是 char Array,那么您可以将 HELLO 更改为 HILLO。例如,
char[] charArr = 'HELLO';
char[1] = 'I'; //you can do this
编程语言具有不可变的数据变量,因此它可以用作键、值对中的键。
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!
我会用简单的例子来解释它
考虑任何字符数组:例如char a[]={'h','e','l','l','o'}; 和一个字符串: 字符串 s=“hello”;
在字符数组上,我们可以执行操作,例如使用迭代数组仅打印最后三个字母; 但是在字符串中,我们必须创建新的 String 对象并复制所需的子字符串,其地址将在新的字符串对象中。
例如
***String s="hello";
String s2=s.substrig(0,3);***
所以 S2 会有“hel”;
评论