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

时间:2014-03-14 06:38:17

标签: java string

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

我这样回答:

  

当我们在像String s1="hello";这样的java中创建一个字符串的时候   对象将在字符串池(你好)中创建, s1 将在   指向你好。现在我们再次String s2="hello";   将不会创建另一个对象,但 s2 将指向hello   因为 JVM 将首先检查是否存在相同的对象   字符串池与否。如果不存在则只创建一个新的,否则不会。

现在假设java允许字符串变量,那么如果我们将 s1 更改为hello world,那么 s2 值也将是hello world所以java String是不可变的。

如果我的回答是正确错误,请问任何正文吗?

12 个答案:

答案 0 :(得分:149)

String由于多种原因是不可变的,这里有一个摘要:

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

话虽如此,String的不变性仅意味着您无法使用其公共API更改它。事实上,你可以使用反射绕过普通的API。请参阅答案 here

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

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

答案 1 :(得分:42)

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

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

根据关于DZone的this文章:

  

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

     

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

答案 2 :(得分:21)

根据关于DZone的this文章最重要的原因:

  

字符串常量池   ...   如果string是可变的,用一个引用更改字符串将导致其他引用的值错误。

     

安全

     

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

希望它会对你有所帮助。

答案 3 :(得分:18)

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

1。字符串常量池的存在

正如Why String is Stored in String Constant Pool文章中所讨论的,每个应用程序都会创建太多字符串对象,以便保存JVM,使其不会首先创建大量字符串对象,然后进行垃圾收集。 JVM将所有字符串对象存储在名为String constant pool的单独内存区域中,并重用该缓存池中的对象。

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

String a = "Naresh";
String b = "Naresh";
String c = "Naresh";

在上面的示例中,值为Naresh的字符串对象将仅在SCP中创建一次,所有引用abc将指向同一个对象但是如果我们试图在a进行更改,例如a.replace("a", "")

理想情况下,a的值应为Nresh,但bc应保持不变,因为作为最终用户,我们仅在a进行更改。我们知道abc所有人都指向同一个对象,所以如果我们在a进行更改,其他人也应该反映这一变化。

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

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

这就是为什么它由JVM特别处理并且被赋予了一个特殊的内存区域。

2。线程安全

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

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

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

3。安全

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

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

4。等级装载

正如Creating objects through Reflection in Java with Example中所讨论的,我们可以使用Class.forName("class_name")方法在内存中加载一个类,它再次调用其他方法来执行此操作。甚至JVM也使用这些方法来加载类。

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

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

5。 HashCode缓存

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

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

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

了解Why String is Immutable and Final in Java

答案 4 :(得分:4)

我读过这篇文章Why String is Immutable or Final in Java,并认为以下可能是最重要的原因:

  

String在Java中是不可变的,因为String对象是缓存的   字符串池。由于缓存的字符串文字在多个之间共享   客户总是有风险,一个客户的行为会影响   所有其他客户。

答案 5 :(得分:2)

字符串是不可变的,因为: -

1. 设计: - 字符串池是可能的,因为String在java中是不可变的,这样Java Runtime节省了大量的java堆空间,因为不同的String变量可以引用相同的String变量池。如果String不是不可变的,那么就不可能进行字符串实习,因为如果任何变量改变了这个值,它也会被反映到其他变量中。

2. 安全性: - 如果String不是不可变的,那么它将对应用程序造成严重的安全威胁。例如,数据库用户名,密码作为String传递以获取数据库连接,并在套接字编程主机和端口详细信息中作为String传递。由于String是不可变的,因此无法更改其值,否则任何黑客都可能更改引用的值,从而导致应用程序出现安全问题。

3. 线程安全: - 由于String是不可变的,因此对于多线程是安全的,并且可以跨不同的线程共享单个String实例。这避免了线程安全的同步使用,字符串是隐式线程安全的

4. classloader: - 在java类加载器中使用字符串,不可变性提供了Classloader正在加载正确类的安全性。例如,假设您尝试加载java.sql.Connection类的实例,但引用的值更改为myhacked.Connection类,可以对数据库执行不需要的操作。

5. 缓存: - 由于String是不可变的,因此其哈希码在创建时被缓存,不需要再次计算。这使得它成为Map中键的理想选择,并且它的处理速度比其他HashMap键对象快。这就是为什么String主要用作Object作为HashMap键。 以上是我能想到的一些原因,它显示了String不变性的好处。它是Java String类的一个很棒的特性,使它变得特别。

答案 6 :(得分:1)

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

我还要补充一点,因为String是不可变的,它对多线程是安全的,并且可以在不同的线程之间共享单个String实例。这避免了对线程安全使用同步,字符串隐式thread safe

答案 7 :(得分:0)

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

提供的另一个实例变量和String类的方法是,您无法在创建后更改String对象。

你添加的原因并不能使字符串永久不变。这就说明了字符串是如何存储在堆中的。字符串池在性能上有很大差异

答案 8 :(得分:0)

在Java中使String不可变的最重要原因是安全性考虑。接下来是缓存

我相信这里给出的其他原因,例如效率,并发性,设计和字符串池,都是因为String in in immutable。例如。可以创建字符串池,因为String是不可变的,而不是相反。

查看Gosling采访记录here

  

从战略角度来看,他们往往更容易摆脱困境。通常情况下你可以用不可变的东西来做你可以做的事情,比如缓存结果。如果将字符串传递给文件open方法,或者将字符串传递给用户界面中标签的构造函数,则在某些API(如许多Windows API中)中传入一个字符数组。该对象的接收者真的必须复制它,因为他们对它的存储寿命一无所知。而且他们不知道对象发生了什么,是否在他们脚下被改变。

     

你最终几乎被迫复制了这个对象,因为你不知道你是否拥有它。关于不可变对象的一个​​好处是答案是,#34;是的,当然你做的。&#34;因为所有权问题,谁有权改变它,并不存在。

     

迫使字符串不可变的一件事是安全性。你有一个文件打开方法。你传递一个字符串给它。然后它在进行OS调用之前进行各种身份验证检查。如果你设法做了一些有效地改变字符串的事情,在安全检查之后和操作系统调用之前,那么繁荣,你就进入了。但字符串是不可改变的,所以这种攻击不起作用。这个确切的例子就是真正需要的   字符串是不可变的

答案 9 :(得分:0)

除了很棒的答案,我想补充几点。与字符串一样,Array保存对数组起始的引用,因此如果您创建两个数组arr1arr2并执行类似arr2 = arr1的操作,则会引用arr2arr1相同,因此改变其中一个的值将导致另一个的变化,例如

public class Main {
    public static void main(String[] args) {
        int[] a = {1, 2, 3, 4};
        int[] b = a;
        a[0] = 8;
        b[1] = 7;
        System.out.println("A: " + a[0] + ", B: " + b[0]);
        System.out.println("A: " + a[1] + ", B: " + b[1]);
        //outputs
        //A: 8, B: 8
        //A: 7, B: 7
    }
}

它不仅会导致代码中的错误,而且恶意用户也会利用这些错误。假设您有一个更改管理员密码的系统。如果newPasswordoldPassword程序更改密码oldPassword,则用户必须先输入adminPass,然后输入adminPass = newPasswordtemp假设新密码与管理员密码具有相同的引用,因此如果oldPassword等于{{1},那么糟糕的程序员可能会在用户输入数据之前创建一个temp变量来保存管理员密码它会更改密码adminPass = temp。有人知道可以轻松输入新密码,永远不会输入旧密码和abracadabra,他有管理员权限。在学习字符串时我不明白的另一件事为什么JVM没有为每个对象创建一个新的字符串,并且在内存中有一个独特的位置,你可以使用new String("str");来做到这一点你不会这样做的原因希望始终使用new是因为它不是内存效率,而且在大多数情况下read more速度较慢。

答案 10 :(得分:0)

如果HELLO是您的字符串,则不能将HELLO更改为HILLO。此属性称为不变性属性。

您可以具有多个指针String变量来指向HELLO String。

但是,如果HELLO是char Array,则可以将HELLO更改为HILLO。例如

char[] charArr = 'HELLO';
char[1] = 'I'; //you can do this

答案:

编程语言具有不变的数据变量,因此可以用作键,值对中的键。 字符串变量用作键/索引,因此它们是不可变的

答案 11 :(得分:-1)

Security的角度来看,我们可以使用这个实际的例子:

DBCursor makeConnection(String IP,String PORT,String USER,String PASS,String TABLE) {

    // if strings were mutable IP,PORT,USER,PASS can be changed by validate function
    Boolean validated = validate(IP,PORT,USER,PASS);

    // here we are not sure if IP, PORT, USER, PASS changed or not ??
    if (validated) {
         DBConnection conn = doConnection(IP,PORT,USER,PASS);
    }

    // rest of the code goes here ....
}