所有分类
  • 所有分类
  • 未分类

Java–String不可变的含义、原因、好处

简介

本文介绍Java的String不可变相关内容,包括:含义、原因、好处。

String不可变的含义

String不可变的含义是:将一个已有字符串”123″重新赋值成”456″,不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。

也就是说:不可变的含义是内部数据不可变,而不是说引用不可变。

示例代码

package com.example.a;

public class Demo {
    public static void main(String[] args) {
        String str= "123";
        str = "456";
        System.out.println(str);
    }
}

下面这张图清楚地解释了代码的执行过程:

执行第一行代码时,在堆上新建一个对象实例 123。str 是指向该实例的引用,是实例在堆上的内存地址而已。

执行第二行代码时,只是改变了 str 这个引用的地址,指向了另一个实例 456。

所以,正如前面所说过的,不可变类是其实例不能被修改的类。 给 str 重新赋值仅仅只是改变了它的引用而已,并不会真正去改变它本来的内存地址上的值。 

String为什么不可变

String的内部数据是一个char数组,是对字符串数组的封装,并且是被final修饰的,创建后不可改变。

package java.lang;

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不可变的好处

对于String来说,好处是:

1. 便于实现字符串池(String pool)

在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String pool的概念,在堆中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。

如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,String pool将不能够实现!

例如下边这个代码,就只会在字符串池创建一个字符串“Hello World!”:

String a = "Hello World!";
String b = "Hello World!";

2. 使多线程安全

看下面这个场景,一个函数appendStr()在不可变的String参数后面加上一段“bbb”后返回。appendSb()负责在可变的StringBuilder后面加”bbb”。

public class test {
  // 不可变的String
  public static String appendStr(String s) {
      s += "bbb";
      return s;
  }

  // 可变的StringBuilder
  public static StringBuilder appendSb(StringBuilder sb) {
      return sb.append("bbb");
  }
  
  public static void main(String[] args) {
      String s = new String("aaa");
      String ns = test.appendStr(s);
      System.out.println("String aaa>>>" + s.toString());
      // StringBuilder做参数
      StringBuilder sb = new StringBuilder("aaa");
      StringBuilder nsb = test.appendSb(sb);
      System.out.println("StringBuilder aaa >>>" + sb.toString());
  }
}

如果程序员不小心像上面例子里,直接在传进来的参数上加上“bbb”。因为Java对象参数传的是引用,所有可变的StringBuffer参数就被改变了。可以看到变量sb在Test.appendSb(sb)操作之,就变成了”aaabbb”。

有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。

在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。

The advantage of immutability comes with concurrency. It is difficult to maintain correctness in mutable objects, as multiple threads could be trying to change the state of the same object, leading to some threads seeing a different state of the same object, depending on the timing of the reads and writes to the said object.

By having an immutable object, one can ensure that all threads that are looking at the object will be seeing the same state, as the state of an immutable object will not change.

3. 避免安全问题

在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。

因为String是不可变的,所以它的值是不可改变的。但由于String不可变,也就没有任何方式能修改字符串的值,每一次修改都将产生新的字符串,如果使用char[]来保存密码,仍然能够将其中所有的元素设置为空和清零,也不会被放入字符串缓存池中,用字符串数组来保存密码会更好。

4. 加快字符串处理速度

由于String是不可变的,保证了hashCode的唯一性,于是在创建对象时其hashCode就可以放心的缓存了,不需要重新计算。这也就是一般将String作为Map的Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。

在String类的定义中有如下代码:

private int hash; // 用来缓存HashCode

String的字符数据可变

简介

String类使用char value[]来存字符数据,它的类型为:private final char value[];

看上去它是不可更改的,因为是final类型。注意:final只是表示不能指向其他地址,它里边的内容是可以更改的。

结论:String是可以更改的,使用反射,value.setAccessible(true),然后修改它即可。

示例

package org.example.a;

import java.lang.reflect.Field;

public class Demo {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        String s = "1234";
        System.out.println("改变前:s=" + s);
        Field f = s.getClass().getDeclaredField("value");
        f.setAccessible(true);
        f.set(s, new char[]{'a', 'b', 'c'});
        System.out.println("改变后:s=" + s);
    }
}

执行结果

改变前:s=1234
改变后:s=abc
8

评论5

请先

  1. String内部数据不可变? final只是表示不能指向其他地址,它里边的内容是可以更改的? 这两句如何理解
    珠光2023 2023-11-07 0
    • String内部数据不可变:String的字符串正常方式是无法更改的。 final只是表示不能指向其他地址,它里边的内容是可以更改的:String内的字符数组引用是final类型,它不能指向其他位置。但是可以修改字符数组数据(通过反射)。
      自学精灵 2023-11-07 2
    • 就像一个final的char[] c=new char[10],他不能给c这个数组赋值c=new char[0]是不被允许的。但是可以修改c[0~9]里面的值
      择人间 2023-11-09 0
    • 手里有个瓶子,瓶子不能变,但是可以装水,可以装酒等
      trokix 2024-02-21 2
显示验证码
没有账号?注册  忘记密码?

社交账号快速登录