Skip to content

String类在我们开发中使用频率相当高,本篇内容将深入介绍String类的一些特性。

1、String 不可变性

String类是不可变的,对String类的每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象。 看下边这段代码:

java
public class string {

    public static void main(String[] argus){
        String oldString = "string";
        String newString = add123(s);
        System.out.println(oldString);
        System.out.println(newString);
    }

    private static String add123(String d){
        return d+="123";
    }
}

当把oldString传给add123方法时,实际传递的是oldString引用的一个拷贝,而在add123方法内部执行+=操作时,由于String的不可变性,实际上是创建了一个新的String对象,因此oldString的值不会发生变化。

过程如下图所示:

2、String类为什么是不可变的?

java
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源码可以看出,Java中String类其实就是对字符数组的封装,在Java中,数组也是对象,所以value也只是一个引用,它指向一个真正的数组对象。而这个数组对象使用private final修饰的,也就是初始化之后不可改变。

在执行了String s = “ABCabc”; 这句代码之后,真正的内存布局应该是这样的:

3、 重载+与Stringbuilder

拼接字符串是java中非常常见的操作,“+”和StringBuilder都可以拼接字符串,在拼接操作较少的情况下,这两种操作都可以使用,但在拼接字符串操作较多的情形下(比如循环中),+操作符会影响性能,更推荐使用StringBuilder

为什么会这样呢?看看这段代码进行反编译后的结果你就知道了:

java
public static void main(String[] argus) {
    String s = "s";
    for (int i = 0; i < 10; i++) {
        s += "s";
    }
    System.out.println(s);
}

“+”操作符拼接字符串实际上也是调用了String Builder方法,从反编译后的结果,可以看到从第8行到34行,执行了循环语句,在每一次循环语句中创建了一个StringBuilder对象(11行),这会生成大量临时对象,造成资源浪费。

4、 字符串常量池

作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能。

字符串常量池的设计:

  • JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
    • 为字符串开辟一个字符串常量池,类似于缓存区
    • 创建字符串常量时,首先检测字符串常量池是否存在该字符串
    • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
  • 实现的基础
    • 实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享
    • 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收

使用new String 和直接赋值创建String对象的区别:

java
String str1 = “abc”;
String str2 = “abc”;
String str3 = “abc”;
String str4 = new String(“abc”);
String str5 = new String(“abc”);

String使用equal和==的区别

先上结论,String对象应该使用equal对象判断。

首先了解equal和==的区别: =**=**操作符:

  1. 用于基本类型的比较,判断值是否相等
  2. 用于引用类型比较:判断引用是否指向堆内存的同一块地址。

**equal** equal是Object类的方法,子类一般会重写该方法。一般用于比较对象的值是否相等。

String对象使用不同的创建方式,equal和==判断的结果的是不同的:

java
String s1 = "ss";              // String 直接创建
String s2 = "ss";              // String 直接创建

String s3 = new String("ss");   // String 对象创建
String s4 = new String("ss");   // String 对象创建

System.out.println(s1 == s2); // true
System.out.println(s1.equals(s2));//true

System.out.println(s3 == s4); // false
System.out.println(s3.equals(s4)); //true

System.out.println(s1 == s3);//false
System.out.println(s1.equals(s3));//true