📜  为什么Java字符串是不可变的?

📅  最后修改于: 2022-05-13 01:55:02.938000             🧑  作者: Mango

为什么Java字符串是不可变的?

在进一步讨论immutability之前,让我们先看看 String 类及其功能,然后再得出任何结论。

这就是字符串的工作方式:

String str = "knowledge";

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

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

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

str = str.concat(" base");

这会将字符串“base”附加到 str。但是等等,这怎么可能,因为 String 对象是不可变的?令你惊讶的是,它是。

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

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

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

为什么字符串本质上是不可变的?

这些是在Java中使 String 不可变的更多原因。这些都是:

  • 如果 String 在Java中不是不可变的,则无法使用 String 池。 JRE 节省了大量的堆空间。同一个字符串变量可以被池中的多个字符串变量引用。如果 String 不是不可变的,则 String interning 也是不可能的。
  • 如果我们不使 String 不可变,它将对应用程序构成严重的安全威胁。例如,数据库用户名、密码作为字符串传递以接收数据库连接。套接字编程主机和端口描述也作为字符串传递。 String 是不可变的,所以它的值不能改变。如果字符串不是不可变的,任何黑客都可以通过更改引用值在应用程序中引起安全问题。
  • String 由于其不可变性,因此对于多线程是安全的。不同的线程可以访问单个“字符串实例”。它消除了线程安全的同步,因为我们隐式地使字符串线程安全。
  • 不变性提供了通过 Classloader 加载正确类的安全性。例如,假设我们有一个实例,我们尝试加载Java.sql.Connection 类,但对 myhacked.Connection 类的引用值的更改对我们的数据库造成了不必要的影响。

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

如果我们没有另一个对“知识”的引用呢?我们会丢失那个字符串。但是,它仍然存在,但由于没有引用而被视为丢失。
再看下面一个例子

Java
// Java Program to demonstrate why
// Java Strings are immutable
 
import java.io.*;
 
class GFG {
    public static void main(String[] args)
    {
        String s1 = "java";
        s1.concat(" rules");
 
        // Yes, s1 still refers to "java"
        System.out.println("s1 refers to " + s1);
    }
}


输出
s1 refers to java

解释:

  1. 第一行非常简单:创建一个新字符串“Java”并将 s1 引用到它。
  2. 接下来,VM 创建另一个新字符串“Java rules”,但没有任何内容引用它。因此,第二个字符串立即丢失。我们达不到。

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

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

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

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

在 String 常量池中,一个 String 对象很可能有一个或多个引用。如果多个引用指向同一个字符串,甚至不知道它,那么如果其中一个引用修改了该字符串值,那就太糟糕了。这就是为什么 String 对象是不可变的。

好吧,现在你可以说,如果有人重写了 String 类的功能怎么办?这就是String 类被标记为 final的原因,因此没有人可以覆盖其方法的行为。