为什么他们决定在Java和。net(和其他一些语言)中使字符串不可变?为什么不让它变呢?


当前回答

哇!我不敢相信这里的错误信息。不可变的字符串与安全性无关。如果某人已经可以访问正在运行的应用程序中的对象(如果你试图防止某人在你的应用程序中“入侵”字符串,就必须假设这一点),那么他们肯定有很多其他可用的黑客机会。

String的不可变性解决了线程问题,这是一个相当新颖的想法。嗯…我有一个被两个不同线程改变的对象。我如何解决这个问题?同步对对象的访问?Naawww……让我们不要让任何人改变对象——这将解决我们所有混乱的并发问题!事实上,让我们让所有对象都是不可变的,然后我们就可以从Java语言中删除synchronized结构。

The real reason (pointed out by others above) is memory optimization. It is quite common in any application for the same string literal to be used repeatedly. It is so common, in fact, that decades ago, many compilers made the optimization of storing only a single instance of a String literal. The drawback of this optimization is that runtime code that modifies a String literal introduces a problem because it is modifying the instance for all other code that shares it. For example, it would be not good for a function somewhere in an application to change the String literal "dog" to "cat". A printf("dog") would result in "cat" being written to stdout. For that reason, there needed to be a way of guarding against code that attempts to change String literals (i. e., make them immutable). Some compilers (with support from the OS) would accomplish this by placing String literal into a special readonly memory segment that would cause a memory fault if a write attempt was made.

在Java中,这被称为实习。这里的Java编译器只是遵循了编译器几十年来所做的标准内存优化。为了解决这些String字面值在运行时被修改的相同问题,Java简单地使String类不可变(即,不提供允许您更改String内容的setter)。如果字符串字面量没有发生转换,字符串就不必是不可变的。

其他回答

Java中的字符串并不是真正不可变的,您可以使用反射和或类加载来更改它们的值。你不应该依赖这个属性来保证安全。 有关示例请参见:Java中的魔术

String不是一个基本类型,但你通常想用值语义来使用它,即像一个值。

价值观是你可以信任的东西,不会在你背后改变。 String str = someExpr(); 你不希望它改变,除非你对str做些什么。

String作为对象自然具有指针语义,为了获得值语义,它也需要是不可变的。

几乎每条规则都有例外:

using System;
using System.Runtime.InteropServices;

namespace Guess
{
    class Program
    {
        static void Main(string[] args)
        {
            const string str = "ABC";

            Console.WriteLine(str);
            Console.WriteLine(str.GetHashCode());

            var handle = GCHandle.Alloc(str, GCHandleType.Pinned);

            try
            {
                Marshal.WriteInt16(handle.AddrOfPinnedObject(), 4, 'Z');

                Console.WriteLine(str);
                Console.WriteLine(str.GetHashCode());
            }
            finally
            {
                handle.Free();
            }
        }
    }
}

不变性很好。参见有效的Java。如果每次传递String时都必须复制它,那么这将是大量容易出错的代码。您还会混淆哪些修改会影响哪些引用。同样地,Integer必须是不可变的才能像int一样,string必须是不可变的才能像原语一样。在c++中,按值传递字符串是这样做的,源代码中没有明确提到。

我知道这是个意外,但是… 它们真的是不可变的吗? 考虑以下几点。

public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
    fixed (char* ptr = s)
    {
        *((char*)(ptr + i)) = c;
    }
}

...

string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3

你甚至可以让它成为一个扩展方法。

public static class Extensions
{
    public static unsafe void MutableReplaceIndex(this string s, char c, int i)
    {
        fixed (char* ptr = s)
        {
            *((char*)(ptr + i)) = c;
        }
    }
}

是什么使下面的工作

s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);

Conclusion: They're in an immutable state which is known by the compiler. Of couse the above only applies to .NET strings as Java doesn't have pointers. However a string can be entirely mutable using pointers in C#. It's not how pointers are intended to be used, has practical usage or is safely used; it's however possible, thus bending the whole "mutable" rule. You can normally not modify an index directly of a string and this is the only way. There is a way that this could be prevented by disallowing pointer instances of strings or making a copy when a string is pointed to, but neither is done, which makes strings in C# not entirely immutable.