为什么Java中String是不可变的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
String 是 Java 语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。它是典型的 Immutable 类,被声明成为 final class,所有属性也都是 final 的(hashCode方法除外)。也由于它的不可变性,类似拼接、裁剪字符串等动作,都会产生新的 String 对象。
不可变对象是指一旦创建完成就不能再被修改的对象。也就说一旦一个对象被赋值了,这个对象的引用或者内部状态都不能被修改,对象所暴露的public方法不允许你修改
使用不可变对象有四个优点:缓存,安全,同步,性能。使用不可变对象可以省去很多不必要的麻烦,而且很对场景下只能使用不可变对象(例如缓存结果)。对于可变对象传参或者赋值的时候,必须要copy这个可变参数的值,因为我们不知道这个可变对象的值何时会被修改。但如果使用的是final类型,就只需要copy这个变量的引用。因为Java确保了final类型的值不会被修改。
String使用一个声明为final的字符数组value来存储值,value一旦赋值后所指向的地址就不能再被修改。虽然数组的值可以修改,但是String类本身并没有提供能够修改value数组值的方法。
/** The value is used for character storage. */
private final char value[];
补充一下:当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变;但对于引用类型的变量,JDK保证的是变量指向的地址不会被改变(即指向的始终是同一个对象),至于地址的值(对象的值)是可以改变的。
在前文中指出使用String这种不可变对象便于缓存,而jdk就为String提供了字符串常量池。字符串是最常使用的数据结构,缓存String并重复使用可以极大的节省Heap空间,因为相同的String变量指向了常量池中相同地址的值。例如以下代码只在字符串常量池中创建一个String对象
String string1 = "abcd";
String string2 = "abcd";
System.out.println(string1 == string2);
//代码输出结果为true
如图所示,如果String是可变的,那修改string1变量所指向的地址的内容,就会导致string2的内容也被修改了
String在Java应用中被大量用于存储敏感信息(用户名,密码,URL等),也被JVM Class Loader用于加载类信息,因此十分有必要保证String的安全
void criticalMethod(String userName) {
// 1.安全检查
if (!isAlphaNumeric(userName)) {
throw new SecurityException();
}
// 2.更新DB
connection.executeUpdate("UPDATE Customers SET Status = 'Active' " +
" WHERE UserName = '" + userName + "'");
}
如上代码片段,方法传入一个userName的变量,第一步先检查改变量是否都是字母数字,然后执行sql 假设String是可变的,且方法已经执行过代码1还未执行代码2的时候,这时候上游调用方还持有参数userName的引用,如果调用方此时修改了userName的值,那就相当于混过了代码1出的安全检查。那么SQL注入的风险就会增加了
“不可变”保证在并发编程模型中,不同Thread不能修改String的值,如果某个Thread修改了一个String值,这个值就会被缓存到StringPool中,也就是说对于String的引用是安全的。
JDK提供了很多hash操作的数据结构:HashMap,HashTable,HashSet等,当在这些类上操作的时候,hashCode()方法就会被频繁的调用。“不可变”保证了String对象的值不会被修改,所以hash值也就不会被修改。对于不变的值就可以缓存起来以便之后调用hasCode()方法的时候就可以直接取出缓存中的值 JDK源码中String代码片段如下:
/** Cache the hash code for the string */
private int hash; // Default to 0
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
可以看到String对象有一个属性hash用于缓存hasCode()第一次计算的结果,之后调用hashCode()方法就直接从缓存中取出来
关于为什么Java中String是不可变的问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。