为什么String在Java中是不可变的?

roc*_*ing 169 java string

我在接受采访时被问到为什么String是不可变的

我这样回答:

当我们在java中创建一个字符串时,String s1="hello";就会在字符串池(hello)中创建一个对象,而s1将指向hello.如果我们再这样做,String s2="hello";那么将不会创建另一个对象但s2将指向,hello 因为JVM将首先检查如果字符串池中存在相同的对象. 如果不存在则只创建一个新对象,否则不创建.

现在,如果假设Java允许串可变那么如果我们改变S1hello world那么S2价值也将hello world因此Java字符串是不可改变的.

如果我的答案是还是错,请问任何人可以告诉我吗?

小智 155

String 由于几个原因是不可变的,这里是一个总结:

  • 安全性:参数通常表示为String网络连接,数据库连接URL,用户名/密码等.如果它是可变的,则可以轻松更改这些参数.
  • 同步和并发:使字符串不可变自动使它们线程安全,从而解决同步问题.
  • 缓存:当编译器优化你的String对象时,它会看到如果两个对象具有相同的值(a ="test",并且b ="test"),那么你只需要一个字符串对象(对于a和b,这两个将指向同一个对象).
  • 类加载:String用作类加载的参数.如果是可变的,则可能导致加载错误的类(因为可变对象改变了它们的状态).

话虽这么说,不变性String只意味着你不能使用它的公共API来改变它.事实上,你可以使用反射绕过普通的API.在这里看到答案.

在您的示例中,如果String是可变的,请考虑以下示例:

  String a="stack";
  System.out.println(a);//prints stack
  a.setValue("overflow");
  System.out.println(a);//if mutable it would print overflow
Run Code Online (Sandbox Code Playgroud)

  • 如何影响安全? (14认同)
  • 关于安全性,如果我对更改连接参数感兴趣,那么在运行时(使用调试器等)会很简单.关于类加载,如果`String`是可变的,那么类加载器将获取传入的字符串,制作副本,而不是更改其副本.当想到可变的`java.lang.String`s的问题时,想一想C++如何解决这个问题(因为它有可变的`std :: string`s. (6认同)
  • 讽刺的是,[Java 中的最佳实践](/sf/ask/621690401/) 是使用 `char[]` 而不是 ` String` 用于密码,因此您可以随后将数组清零。这避免了某些其他代码保留对密码的引用的可能性,并立即从内存中删除密码,而不是等待垃圾收集器。所以`String`的不变性是一个安全问题! (3认同)
  • 如果可能,有人可以通过示例解释类加载吗? (2认同)

Ale*_*hew 43

Java开发人员认为,由于以下方面设计,效率和安全性,字符串是不可变的.

设计 字符串在java堆中的特殊内存区域中创建,称为"字符串实习池".在创建新String时(不是在使用String()构造函数的情况下或者在内部使用String()构造函数创建新String对象的任何其他String函数; String()构造函数总是在池中创建新的字符串常量,除非我们调用方法intern())变量,它搜索池以检查它是否已经存在.如果存在,则返回现有String对象的引用.如果String不是不可变的,则使用一个引用更改String将导致其他引用的值错误.

根据DZone的这篇文章:

安全 字符串被广泛用作许多java类的参数,例如网络连接,打开文件等.字符串不是不可变的,连接或文件将被更改并导致严重的安全威胁.可变字符串也可能导致Reflection中的安全问题,因为参数是字符串.

效率 字符串的哈希码经常在Java中使用.例如,在HashMap中.不可变保证hashcode将始终相同,因此可以缓存它而不必担心更改.这意味着,每次使用时都不需要计算哈希码.

  • 您对字符串池的理解不正确.字符串*常量*是在实习池中创建的,但是很可能有多个具有相同文本的字符串对象.我同意字符串是不可变的,这样就可以实现池化,但是没有那么多池化. (6认同)
  • 它不仅仅是使用字符串构造函数-几乎所有创建新字符串的*任何操作,例如子字符串,拆分,concat等都将创建新字符串。编译时常量在这里是特殊情况,而不是规范。 (2认同)
  • @JonSkeet - 所有这些答案都说不变性提高了“安全性”,但没有解释如何。它们都链接到一个模糊的 dzone 文章,这也无济于事。答案/链接没有解释在代码运行时如何更改可变字符串。你能解释一下吗? (2认同)

JDG*_*ide 21

根据DZone 这篇文章最重要的原因:

String Constant Pool ...如果string是可变的,用一个引用更改字符串将导致其他引用的值错误.

安全

字符串被广泛用作许多java类的参数,例如网络连接,打开文件等.字符串不是不可变的,连接或文件将被更改并导致严重的安全威胁....

希望它会对你有所帮助.


Nar*_*shi 21

我们不能确定Java设计师在设计时实际想到的是什么,String但我们只能根据字符串不变性的优势来总结这些原因,其中一些是

1.字符串常量池的存在

正如在字符串常量池文章中存储的为什么字符串中所讨论的那样,每个应用程序都会创建太多的字符串对象,以便从首先创建大量字符串对象以及然后垃圾收集它们来保存JVM.JVM将所有字符串对象存储在名为String常量池的单独内存区域中,并重用该缓存池中的对象.

每当我们创建一个字符串文字时,JVM首先会看到该文字是否已经存在于常量池中,如果它存在,那么新引用将开始指向SCP中的同一个对象.

String a = "Naresh";
String b = "Naresh";
String c = "Naresh";
Run Code Online (Sandbox Code Playgroud)

在与值上面的例子中的字符串对象Naresh将在SCP获得创建只有一次,所有参考a,b,c会如果我们试图在改变指向同一个对象,但什么aa.replace("a", "").

理想情况下,a应该有值Nresh,但是b,c应该维持不变,因为我们一直在变动中的最终用户a只.而我们知道a,b,c所有都指向同一个对象,因此,如果我们做出改变a,其他人也应反映这一变化.

但字符串不变性使我们免于这种情况,并且由于字符串对象字符串对象的不变性Naresh将永远不会改变.因此,当我们进行任何更改a而不是更改字符串对象时,NareshJVM会创建一个新对象,将其分配给该对象a,然后在该对象中进行更改.

所以字符串池是唯一可能的,因为String的不变性,如果String不是不可变的,那么缓存字符串对象并重新使用它们是不可能的,因为任何变量都会改变值并损坏其他变量.

这就是为什么它由JVM非常特殊地处理并且被赋予了特殊的存储区域.

2.螺纹安全

当多个线程对其进行操作时,该对象称为线程安全的,但它们都不能破坏其状态,并且对象在任何时间点都为每个线程保持相同的状态.

因为我们在创建它之后不能修改任何不可变对象,这使得每个不可变对象在默认情况下都是线程安全的.我们不需要对其应用任何线程安全措施,例如创建同步方法.

因此,由于其不可变性,字符串对象可以由多个线程共享,即使它被许多线程操纵,也不会改变其值.

3.安全

在每个应用程序中,我们需要传递几个秘密,例如用户的用户名\密码,连接URL,并且通常,所有这些信息都作为字符串对象传递.

现在假设如果String本质上不是不可变的,那么它将对应用程序造成严重的安全威胁,因为允许这些值被更改,如果允许,那么由于错误编写的代码或任何其他人,这些可能会被更改.有权访问我们的变量引用.

4.班级装载

正如通过使用示例在Java中通过反射创建对象中所讨论的,我们可以使用Class.forName("class_name")方法在内存中加载一个类,该类再次调用其他方法来执行此操作.甚至JVM也使用这些方法来加载类.

但是如果你清楚地看到所有这些方法都接受类名作为字符串对象,那么在java类加载中使用字符串,不可变性提供了正确的类加载的安全性ClassLoader.

假设String不是不可变的,并且我们正在尝试加载java.lang.Object其间的更改org.theft.OurObject,现在我们所有的对象都有一个人可以用来做不需要的事情的行为.

5. HashCode缓存

如果我们要对任何对象执行任何散列相关操作,我们必须覆盖该hashCode()方法并尝试使用对象的状态生成准确的散列码.如果对象的状态发生了变化,这意味着它的哈希码也应该改变.

因为String是不可变的,所以一个字符串对象所持有的值永远不会被更改,这意味着它的哈希码也不会改变,这使String类有机会在对象创建期间缓存其哈希码.

是的,String对象在创建对象时缓存其哈希码,这使得它成为哈希相关操作的理想选择,因为哈希码不需要再次计算,这节省了我们一些时间.这就是String主要用作HashMap键的原因.

阅读更多关于为什么String是Java不可变和最终的.

  • 请披露任何[附属机构](https://stackoverflow.com/help/promotion),不要使用该网站作为通过发布来宣传您的网站的方式.请参阅[如何写出一个好的答案?](https://stackoverflow.com/help/how-to-answer). (2认同)

Tho*_*Tho 10

以下可能是最重要的原因:

String 在 Java 中是不可变的,因为 String 对象缓存在 String pool 中。由于缓存的字符串文字在多个客户端之间共享,因此始终存在风险,其中一个客户端的操作会影响所有另一个客户端。

参考:为什么字符串在 Java 中是不可变的或最终的


Aks*_*hay 5

你是对的。String在java中使用String Pool字面量的概念。当创建一个字符串时,如果该字符串已经存在于池中,则将返回现有字符串的引用,而不是创建一个新对象并返回其引用。如果字符串不是不可变的,则用一个引用更改字符串将导致其他参考值错误。

我还要添加一件事,因为它String是不可变的,所以对于多线程来说是安全的,并且单个 String 实例可以在不同线程之间共享。这避免了使用同步来保证线程安全,字符串是隐式的thread safe


Kic*_*ick 0

String 类意味着FINAL您无法创建任何类来继承它并更改基本结构并使 Sting 可变。

提供的另一件事是 String 类的实例变量和方法,一旦创建就无法更改String对象。

您添加的内容根本不会使字符串不可变。这一切都说明了字符串如何存储在堆中。而且字符串池在性能上产生了巨大差异

  • 如果类声明为final,则意味着该类不能被继承,但这并不意味着该类的实例字段不能更改,因此该类是不可变的。 (11认同)