面试小知识(1)String为什么不可变?

(:这个专栏我会总结一下我感觉挺有意思的面试题知识点,巩固自己记忆,希望还能帮助到你们,
—————————————————————————————————
不可变对象可以理解为:

如果一个对象,在它正确创建完成之后,不能再改变它的状态(包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变),那么这个对象就是不可变的。

String str = "我是1";
str = "我是2";

也许有人会对上面的代码有疑惑,str不是改变了值了吗,String怎么会是不可变呢?

由于字符串存放在JVM的常量池中,而对象引用存放在JVM栈。所以,上述的代码只不过是str的引用由指向常量池中的“我是1”,变成了指向“我是2”。
面试小知识(1)String为什么不可变?_第1张图片
什么是常量池?

常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。

因为常量池是编译期被确定,所以常常又会引出下面的问题。

//代码1
String s0=”hello”;
String s1=”hello”;
String s2=”hel” + “lo”;
System.out.println( s0==s1 );
System.out.println( s0==s2 );
结果为:
true
true

//代码2
String s0=”hello”;
String s1=new String(”hello”);
String s2=”hel” + new String(“lo”);
System.out.println( s0==s1 );
System.out.println( s0==s2 );
System.out.println( s1==s2 );
结果为:
false
false

为什么会出现这种情况呢?

代码1中s0和s1的”hello”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”hel”和”lo”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中”hello”的一个引用。

而代码2中用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,堆内存中开辟一片新空间存放新对象。所以他们其实不是一个地址,为false。

说起new String() ,又有一道面试题是这样问的:

String str = new String("abc");

上面这条代码中,一共创建了几个对象?

答案是:两个或一个

如果代码如下,且aaa这个字符串之前没有用过,程序会在堆内存中开辟一片新空间存放新对象,同时会将”aaa”字符串放入常量池,相当于创建了两个对象。

String str = new String(“aaa”);

如果代码如下,则只会在堆内存中开辟一片新空间存放新对象。

String str1 = "aaa";
String str = new String(“aaa”);

上面说了这么多,那么String为什么不可变?

在JDK1.8中,我们打开String的源码
面试小知识(1)String为什么不可变?_第2张图片
映入眼帘的就是几个final修饰的类和变量。

之所以String不能被继承,是因为该类是final修饰的;

之所以String是不可变的,是因为char数组是final修饰

虽然成员变量hash并没有用final声明,但是由于第一次调用hashCode()会重新计算hash值,并且以后调用会使用已缓存的值,当然最关键的是每次计算时都得到相同的结果,所以也保证了对象的不可变。

源码如下:

/**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * 
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * 
* using {@code int} arithmetic, where {@code s[i]} is the * ith 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不是不可变吗,为什么我们可以调用String中的substring, replace, replaceAll, toLowerCase方法改变它?

String a = "ABCabc";  
a = a.replace('A', 'a');  
System.out.println("a = " + a);  

我们需要明确的是,replace等方法并不能改变字符串本身的,只是replace方法能返回一个新的处理后的字符串。

源码如下:

    public String replace(char oldChar, char newChar) {
     
        if (oldChar != newChar) {
     
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
     
                if (val[i] == oldChar) {
     
                    break;
                }
            }
            if (i < len) {
     
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
     
                    buf[j] = val[j];
                }
                while (i < len) {
     
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

StringBuffer类弥补了String类的不足,StringBuffer类的内容可以修改。

String类不可变的好处有什么?

  • 只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串引用可以指向池中的同一个字符串。但如果字符串是可变的,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
  • 如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入数据库,以获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
  • 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。
  • 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
  • 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算,这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串的原因。

如有错误,敬请指出!
—————————————————————————————————
参考:
https://www.nowcoder.com/tutorial/94/b479698283ed4a36a0ff3e53e54794ff
https://zhidao.baidu.com/question/212316190.html
https://www.cnblogs.com/xzwblog/p/7230366.html

你可能感兴趣的:(小知识,面试题,java,面试,string)