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

    String 是一个不可变的,由 final 修饰的类。

    不可变类是指其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并且在对象的整个生命周期内固定不变。为了使类不可变,要遵循下面五条规则:

    1. 不要提供任何会修改对象状态的方法。

    2. 保证类不会被扩展。 一般的做法是让这个类为 final 的,防止类被继承,破坏该类的不可变行为。

    3. 使所有的域都是 final 的。

    4. 使所有的域都成为私有private的。 防止客户端获得访问被域引用的可变对象的权限,并防止客户端直接修改这些对象。

    5. 确保对于任何可变性组件的互斥访问。 如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。

    以上五条也是java中不可变类的特征

    String str=new String();

    str=”123”;

    Str=”456”;

    执行第一行代码时,在堆上新建一个对象实例 123 ,str 是一个指向该实例的引用,引用包含的仅仅只是实例在堆上的内存地址而已。执行第二行代码时,仅仅只是改变了 str 这个引用的地址,指向了另一个实例 456。所以,正如前面所说过的,不可变类只是其实例不能被修改的类。  str 重新赋值仅仅只是改变了它的引用(也就是指向的内存地址)而已,并不会真正去改变它本来的内存地址上的值。这样的好处也是显而易见的,最简单的当存在多个 String 的引用指向同一个内存地址时,改变其中一个引用的值并不会对其他引用的值造成影响。

    示例:

        final char[] value = {'a', 'b', 'c'};

    value[2] = 'd';

    这时候的 value 对象在内存中已经是 a b d 了。其实 final 修饰的仅仅只是 value 这个引用,你无法再将 value 指向其他内存地址,例如下面这段代码就是无法通过编译的:

        final char[] value = {'a', 'b', 'c'};

        value = {'a', 'b', 'c', 'd'};//指向了新的内存地址

    所以仅仅通过一个 final 是无法保证其值不变的,如果类本身提供方法修改实例值,那就没有办法保证不变性了。Effective Java 中的第一条原则 不要提供任何会修改对象状态的方法 。

    String 类中并没有提供任何可以改变其值的方法。相比 final 而言,这更能保障 String 不可变。

    String 对象在内存中的位置:

     String str1 = "123";

      String str2 = new String("123");

      System.out.println(str1 == str2);

    结果显然是 false我们都知道字符串常量池的概念,JVM 为了字符串的复用,减少字符串对象的重复创建,特别维护了一个字符串常量池。第一种字面量形式的写法,会直接在字符串常量池中查找是否存在值 123,若存在直接返回这个值的引用,若不存在创建一个值为 123 String 对象并存入字符串常量池中。而使用 new 关键字,则会直接在堆上生成一个新的 String 对象,当字符常量池中没有‘HelloFlyapi’时,会在常量池中也创建一个对象是副本;当常量池中已经存在了,就不会创建新的了所以本质上 str1 str2 指向的内存地址是不一样的。

    为此,String 类提供了一个 native 方法 intern() 用来将这个对象加入字符串常量池:

    String str1 = "123";

      String str2 = new String("123");

      str2=str2.intern();

      System.out.println(str1 == str2);

    打印结果为 truestr2 调用 intern() 函数后,首先在字符串常量池中寻找是否存在值为 123 的对象,若存在直接返回该对象的引用,若不存在,加入 str2 并返回。上述代码中,常量池中已经存在值为 123 str1 对象,则直接返回 str1 的引用地址,使得 str1 str2 指向同一个内存地址。

    这部分其实比较麻烦,需要分版本讨论。在1.7之前,intern方法是如果常量池没有该字符串则新建一个对象,反之则直接返回常量池中字符串对象引用,而1.7之后,如果常量池没有该字符串,则将常量池的对象引用指向你创建的字符串对象,这部分和1.7之前的区别是,它并不会新建一个对象,而是指向堆上已有的字符串对象,反之,则和以前一样,。

    常量池是方法区的一部分,所以字符串对象是在方法区里的。

    其实,通过反射可以破坏string的不可变性,通过string.class()获得类对象,然后调用类对象的getDeclaredField()方法,并且setaccessible方法为true,最后在get()得到字符串数组后(char[] value = (char[]) field.get(str);)就可以修改了

  • 相关阅读:
    JavaScript 开发的45个经典技巧
    LINQ
    迭代器
    【工具篇】抓包中的王牌工具—Fiddler (1-环境搭建)
    浏览器本地数据库 IndexedDB 基础详解
    Python爬虫实践 -- 记录我的第二只爬虫
    美团App用户界面分析
    APP测试要点—UI、功能测试
    Emmagee--APP性能测试工具的基本使用
    APP测试工具与技术
  • 原文地址:https://www.cnblogs.com/wl889490/p/13053535.html
Copyright © 2011-2022 走看看