我在不可变字符串上写了下面的代码。

public class ImmutableStrings {

    public static void main(String[] args) {
        testmethod();
    }

    private static void testmethod() {
        String a = "a";
        System.out.println("a 1-->" + a);
        a = "ty";
        System.out.println("a 2-->" + a);
    }
}

输出:

a 1-->a  
a 2-->ty

这里变量a的值被改变了(尽管许多人说不可变对象的内容不能被改变)。但是说字符串是不可变的到底是什么意思呢?你能给我解释一下这个话题吗?

来源:https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html


当前回答

你不是在改变赋值语句中的对象,而是用另一个不可变对象替换了一个不可变对象。Object String("a")不会变成String("ty"),它会被丢弃,对ty的引用会被写入a中。

相反,StringBuffer表示一个可变对象。你可以这样做:

StringBuffer b = new StringBuffer("Hello");
System.out.writeln(b);
b.append(", world!");
System.out.writeln(b);

在这里,您没有重新分配b:它仍然指向相同的对象,但该对象的内容已经更改。

其他回答

在进一步讨论不可变性之前,让我们在得出任何结论之前稍微了解一下String类及其功能。

这是String的工作原理:

String str = "knowledge";

这,像往常一样,创建一个包含“知识”的字符串,并为其分配一个引用str。简单吗?让我们执行更多的函数:

 String s = str;     // assigns a new reference to the same string "knowledge"

让我们看看下面的语句是如何工作的:

  str = str.concat(" base");

这将追加一个字符串“base”到str。但等一下,这是怎么可能的,因为字符串对象是不可变的?出乎你的意料,它是。

当执行上述语句时,虚拟机接受String str的值,即。“knowledge”并附加“base”,赋予我们“knowledge base”这个值。现在,由于字符串是不可变的,VM不能将这个值分配给str,所以它创建了一个新的String对象,给它一个值“知识库”,并给它一个引用str。

这里需要注意的一点是,虽然String对象是不可变的,但它的引用变量不是。这就是为什么在上面的例子中,引用被用来引用一个新形成的String对象。

在上面例子的这一点上,我们有两个String对象:第一个是我们用值"knowledge"创建的,由s指向,第二个是"knowledge base",由str指向。但是,从技术上讲,我们有三个String对象,第三个是concat语句中的文字"base"。

关于字符串和内存使用的重要事实

如果我们对“知识”没有另一种指称会怎样?我们会失去那个字符串。然而,它仍然存在,但由于没有参考资料,将被视为丢失。 下面再看一个例子

String s1 = "java";
s1.concat(" rules");
System.out.println("s1 refers to "+s1);  // Yes, s1 still refers to "java"

发生了什么:

第一行非常简单:创建一个新的String "java",并将s1引用给它。 接下来,虚拟机创建另一个新的字符串“java rules”,但是什么都没有 指它。因此,第二个字符串立即丢失。我们够不到 它。

引用变量s1仍然引用原始字符串“java”。

几乎每个应用于String对象以修改它的方法都会创建新的String对象。那么,这些String对象在哪里呢?这些都存在于内存中,任何编程语言的关键目标之一就是有效地利用内存。

随着应用程序的增长,字符串字面值占用大量内存是很常见的,这甚至会导致冗余。因此,为了使Java更高效,JVM留出了一个特殊的内存区域,称为“字符串常量池”。

当编译器看到String字面值时,它会在池中查找String。如果找到匹配,则对新文字的引用将指向现有的String,并且不会创建新的String对象。现有的String只是多了一个引用。下面是使String对象不可变的要点:

在String常量池中,一个String对象可能有一个或多个引用。如果几个引用指向同一个String而不知道它,如果其中一个引用修改了该String值,那就不好了。这就是为什么String对象是不可变的。

现在你可以说,如果有人重写了String类的功能呢?这就是String类被标记为final的原因,这样任何人都不能重写其方法的行为。

在这里看到的

class ImmutableStrings {

    public static void main(String[] args) {
        testmethod();
    }

    private static void testmethod() {
    String a="a";
    System.out.println("a 1-->"+a);
    System.out.println("a 1 address-->"+a.hashCode());

    a = "ty";
    System.out.println("a 2-->"+a);

       System.out.println("a 2 address-->"+a.hashCode());
    }
}

输出:

a 1-->a
a 1 address-->97
a 2-->ty
a 2 address-->3717

这表明无论何时修改不可变字符串对象a的内容都会创建一个新对象。也就是说,你不允许改变不可变对象的内容。这就是为什么两个对象的地址是不同的。

不可变对象是创建后状态不能修改的对象。

a = "ABC" <——immutable object。"a"保存对对象的引用。 并且,a = "DEF" <——另一个不可变对象,"a"现在保存着对它的引用。

一旦你分配了一个字符串对象,该对象就不能在内存中被改变。

总之,您所做的就是将“a”的引用更改为一个新的字符串对象。

我认为下面的代码可以明确区别:

String A = new String("Venugopal");
String B = A;

A = A +"mitul";

System.out.println("A is " + A);
System.out.println("B is " + B);

StringBuffer SA = new StringBuffer("Venugopal");
StringBuffer SB = SA;

SA = SA.append("mitul");

System.out.println("SA is " + SA);
System.out.println("SB is " + SB);

字符串是不可变的,这意味着你不能改变对象本身,但是你可以改变对对象的引用。

当你执行a = "ty"时,你实际上是在改变a对一个由String字面值"ty"创建的新对象的引用。

改变一个对象意味着使用它的方法来改变它的一个字段(或者字段是公共的而不是final的,这样它们就可以从外部更新而不需要通过方法访问它们),例如:

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"