zoukankan      html  css  js  c++  java
  • String为什么是不可变的?

    面试官Q1:请问为什么String是不可变的,能谈谈吗?

    我们知道不管是面试初级、中级还是高级Java开发工程师,String永远都是一个绕不开的话题,而且问的问题也是各不相同,下面我们从几个角度来看看为什么String是不可变的?

    什么是不可变对象?

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

    我们来看下面一段代码:

    public class Demo {
        String str = "ABC";
        System.out.println("s = " + str);
    
        str = "123";
        System.out.println("s = " + str);
    }

    打印结果为:

    1s = ABC
    2s = 123

    对于上述代码,我们简单的分析一下:首先创建一个String对象str,然后让str的值为“ABC”,然后又让str的值为“123”。从打印结果可以看出,str的值确实改变了。

    那还说String对象是不可变的呢?

           这里存在一个误区:str只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,放在堆中,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象,而这个引用存放在Java虚拟机栈栈帧的局部变量表中。也就是说,str只是一个引用,它指向了一个具体的对象,当str=“123”; 这句代码执行过之后,又创建了一个新的对象“123”, 而引用str重新指向了这个新的对象,原来的对象“ABC”还在内存中存在,并没有改变。

    我们用一张内存结构图来看看整个变化过程:

    其实上面的"ABC","123"是字符串常量,按照JVM规范应该是存放在方法区的常量池里面。但是Java1.7之后HotSpot虚拟机并没有区分方法区和堆,所以,这里统一就当做是放在堆里面的吧。

    String源码构成

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    
        /** Cache the hash code for the string */
        private int hash; // Default to 0

    通过源码我们可以知道String底层是由char数组构成,我们创建一个字符串对象的时候,其实是将字符串保存在char数组中,因为数组是引用对象,为了防止数组可变,JDK加了final修饰,但是加了final修饰的数组只是代表了引用不可变,不代表数组内容不可变,因此JDK为了真正防止不可变,又加了private修饰符。

    String对象是真的不可变吗?

          从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中,value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗?我们来看下面的代码:

    final int[] value={1,2,3}
    int[] another={4,5,6};
    value=another;    //编译器报错,final不可变

    value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定

    final int[] value={1,2,3};
    value[2]=100;  //这时候数组里已经是{1,2,100}

    所以String是不可变,关键是因为设计源代码的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final。

    不可变有什么好处?

    1、多线程下安全性

    最简单地原因,就是为了安全。因为String是不可变的,因此多线程操作下,它是安全的,我们来看下面一段代码:

    public String get(String str){
        str += "aaa";
        return str;
    }

    试想一下,如果String是可变的,那么get方法内部改变了str的值,方法外部str也会随之改变。

    2、类加载中体现的安全性

    类加载器要用到字符串,不可变提供了安全性,以便正确的类被加载,例如你想加载java.sql.Connection类,而这个值被改成了xxx.Connection,那么会对你的数据库造成不可知的破坏。

    3、使用常量池可以节省空间

    像下面这样字符串one和two都用字面量"something"赋值。它们其实都指向同一个内存地址

    String one = "someString";
    String two = "someString";

    这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。

    总结了这么多,面试答案就靠大家自己总结了,或者可以通过留言分享给他人哟。

  • 相关阅读:
    Python并发编程:协程-greenlet模块
    Python并发编程:协程介绍
    Python并发编程:多线程-进程池与线程池
    Python并发编程:多线程-线程queue
    程序员能力矩阵(好到这个好有压力...)
    如何让搜索引擎抓取AJAX内容? 转
    使用ReSharper打造团队代码检查流程
    JQuery Easy Ui dataGrid 数据表格 -->转
    TCP/IP协议(1):各层协议帧格式
    VC调试小结
  • 原文地址:https://www.cnblogs.com/shanheyongmu/p/9583010.html
Copyright © 2011-2022 走看看