深入了解String类及字符串拼接问题

String类

1.String概述

String 类代表字符串。Java程序中的所有字符串文字(例如"abc" )都被实现为此类的实例。用双引号表示。字符串是常量;它们的值在创建之后不能更改。

2.String源码分析

//被final修饰不可被继承
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
     
    /** The value is used for character storage. */
    //被final修饰表示为常量
    private final char value[];

当我们进入String这个类时,我们可以发现String类被final修饰,而被final修饰的类是不能被继承的。
String底层使用一个字符数组来维护的。从上面的源码可以看到String类定义了一个私有的被final修饰的字符数组。private final char value[]; 它是不可改变的。所以说String是常量不可改变。

3.创建String对象的两种方式

方式一:直接赋值

String str1="hello";//直接赋值的方式

方式二:通过构造器

String str2=new String("hello");//实例化的方式

那么这两种创建方式有区别吗?肯定是有区别的,它们在Java虚拟机中的创建过程是不一样的。下面是图解过程:
深入了解String类及字符串拼接问题_第1张图片
从上面我们可以看到直接赋值的方式,str1它首先会去方法区的常量池中找,有“hello”这个对象就直接引用,没有就创建一个新的对象“hello”,存放在常量池中。而new的方式首先会在堆中创建一个对象,然后再去常量池中找,有直接引用,没有就创建一个新的对象“hello”。直接赋值创建0或1个对象,而new的方式创建1或2个对象。为了提升Java虚拟机的性能和减少内存的开销,避免字符串的重复创建,尽量少使用new的方式创建String对象。
代码分析

public static void main(String[] args) {
     
        //去找方法区的常量池中找,发现没有对象"hello",
        //创造一个对象"hello"假设它的地址为 0x1234
        String str1 = "hello";
        //在堆中创建一个对象"hello"假设它的地址为 0x5678 
        //然后引用常量池中的"hello"的地址0x1234
        String str2 = new String("hello");
        //将str2的地址0x5678 赋给str3
        String str3 = str2;
        //直接去找方法区的常量池中找,发现有"hello",
        //直接引用它的地址0x1234。
        String str4 = "hello";
        System.out.println(str1==str2);//false
        System.out.println(str1==str3);//false
        System.out.println(str3==str2);//true
        System.out.println(str1==str4);//true
    }

参考连接
说明:String中==比较的地址是否相同。equals比较的是内容是否相同。
上面的代码中

  1. str1的地址为:0x1234。
  2. str2的地址为:0x5678。
  3. str3的地址为:0x5678。
  4. str4的地址为:0x1234。

4.String字符串拼接

方式一:使用+拼接字符串

   
   //常量拼接
    String str4="hello"+"word";
    //变量拼接
    String str2 = "hello";
    String str3=str2+"word";

方式二:使用concat方法拼接

  String str5 = str2.concat("word");

当然StringBuffer和StringBuilder 的append()方法也能实现拼接,
下面我们来看一下拼接的代码实现

 public static void main(String[] args) {
     
        String str1 = "helloword";
        String str2 = "hello";
        //变量拼接
        String str3=str2+"word";
        //常量拼接
        String str4="hello"+"word";
        final String  str6 = "hello";
        String str7=str6+"word";
        //concat方法拼接
        String str5 = str2.concat("word");
        System.out.println(str1==str3);//false
        System.out.println(str1==str4);//true
        System.out.println(str1==str5);//false
        System.out.println(str1==str7);//true
    }

为什么拼接后同样是helloword,有些相等有些不等呢?让我们来分析一下源码。
常量拼接
常量拼接和直接赋值其实差不多,拼接成 "helloword"后它首先会去方法区的常量池中找,有 "helloword"直接引用,没有就创建一个 “helloword”,存放在常量池中,然后引用。
注意:被final修饰的变量是常量,属于常量拼接。

         final String  str6 = "hello";
        String str7=str6+"word";

变量拼接
通过debug我们可以发现,它首先调用了StringBuilder的构造器,创建StringBuilder实例

 public StringBuilder() {
     
        super(16);
    }

然后调用StringBuilder的append方法,追加"hello"和"word"

 @Override
    public StringBuilder append(String str) {
     
        super.append(str);
        return this;
    }

最后调用StringBuilder的toString方法,返回new出来的String

   @Override
    public String toString() {
     
        // Create a copy, don't share the array
        return new String(value, 0, count); 
    }

通过上面的代码我们可以看出,变量拼接最终调用的是StringBuilder的toString方法,而它返回的是new出来的String对象。而new的方式首先会在堆中创建一个对象,然后再去常量池中找,有直接引用,没有就创建一个新的对象。
concat方法拼接

public String concat(String str) {
     
        int otherLen = str.length();
        if (otherLen == 0) {
     
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

通过源码我们也可以看到,经过concat方法,其实是new了一个新的String和上面的变量拼接是一个道理。

你可能感兴趣的:(java,字符串)