String in Java

String 在Java中使用的非常多,只要我们写代码可能就会用到它,但我们真的了解它吗?

一、字符串的创建

1.1、使用双引号申明

在声明字符串时,直接使用双引号声明出来的String对象会直接存储在常量池中

        String str1="hello";
        String str2="hello";
        System.out.println(str1==str2);//true

在使用双引号申明"hello"时,jvm会检查常量池中是否有该对象,有就返回它的引用,没有就创建一个将其添加到常量池中,再返回它的引用。因此str1==tr2结果为true。
在这里插入图片描述

1.2使用new关键字创建String对象

看看下面这行代码:

String str1=new String("hello");

经常会遇到有人问上面这行代码创建了几个对象这种问题,事实上我写这篇博客的起因就是它,原谅我的无知。
上面那行代码总共创建了两个对象,一个是常量池中的“hello”,一个是通过new创建的保存在堆上的String对象。
看下它的字节码就很清楚了。

         0: new           #2                  // class java/lang/String 创建String对象,但还没调用它的<init>方法
         3: dup
         4: ldc           #3                  // String hello	从常量池中加载"hello"对象到操作数栈顶
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V	调用构造方法,并将"hello"传进去。
         9: astore_1
        10: return

在类被加载时"hello"将会在常量池被创建,接着再调用new指令,JVM会在堆中创建一个String对象,并调用器构造函数,引用常量池中的"hello"字符串

再来看看下面的new String(“hello”)创建了几个对象

String str1="hello";
String str2=new String("hello");

此时new String(“hello”)实际上只创建了一个。因为在第一行hello"会在常量池被创建,第二行因为是new指令,JVM会在堆中创建一个String对象,但是它引用了常量池已经存在的"hello"

二、字符串拼接

字符串拼接可以分为分多钟情况。
2.1、字面量之间的+

 String str1="hello"+"world";

字节码如下:
在这里插入图片描述
可以看到对于这种字面量之间的相加,在编译时会被拼接起来。从ldc指令可以看出,它是将拼接后的"helloworld"加载到操作数栈顶,这种字面量的相加只会产生一个对象。
现在我们知道了字面量相加的本质来看一个问题。

        String str1="hello";
        String str2="he"+"llo";
        System.out.println(str1==str2);

str1和str2相等吗,答案是相等的,他们指向的是常量池中的同一个string对象。首先会在常量池中查看有没有"hello",第一次肯定没有,没有就创建,然后将它的引用返回给str1,对于"he"+“llo"在编译时会被拼接为"hello”,然后jvm会取常量池看有没有,因为"hello"已经存在了,就直接返回它的引用,所以str1==str2成立。
在这里插入图片描述

2.2、变量之间的+

        String str1="hello";
        String str2="world";
        String str3=str1+str2;

这3行代码一共会产生三个对象,其中两个是常量池中的"hello"和"world",还有一个是在堆中new出来的StringBuild对象。
字节码如下:

         0: ldc           #2                  // String hello
         2: astore_1
         3: ldc           #3                  // String world
         5: astore_2
         6: new           #4                  // class java/lang/StringBuilder
         9: dup
        10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        13: aload_1
        14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        17: aload_2
        18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        24: astore_3
        25: return

简单的解释下:在字节码的第0行ldc指令,会将常量池中的"hello"加载到操作数栈顶,然后第2行astore_1指令会将操作数栈顶元素也就是"hello"保存到本地变量表1的位置,然后在第3行又将常量池中的"world"加载到操作数栈顶,在第5行将栈顶元素"world"保存到本地变量表2的位置。在第6行会通过new创建一个StringBuilder对象,在第13行执行aload_1指令将本地变量表1位置保存的"hello"加载到操作数栈顶,然后调用StringBuilder的append方法将"hello"传进去,第17,18行的指令调用了StringBuilder的append方法将“world”传进去。从字节码可以看出,这种字符串变量之间的相加实际上会调用StringBuilder的append方法将所有字符串添加进来。
扩展
很多人会告诉我们不要在循环中做字符串的相加,是因为在循环的过程中每循环一次就会创建一个StringBuilder对象。

        String str="";
        for (int i=0;i<10;i++){
            str=str+String.valueOf(i);
        }
        System.out.println(str);

字节码

  stack=2, locals=3, args_size=1
         0: ldc           #2                  // String
         2: astore_1
         3: iconst_0
         4: istore_2
         5: iload_2
         6: bipush        10
         8: if_icmpge     39
        11: new           #3                  // class java/lang/StringBuilder
        14: dup
        15: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        18: aload_1
        19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        22: iload_2
        23: invokestatic  #6                  // Method java/lang/String.valueOf:(I)Ljava/lang/String;
        26: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        29: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        32: astore_1
        33: iinc          2, 1
        36: goto          5
        39: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        42: aload_1
        43: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        46: return

这里主要看下第8行的字节码指令if_icmpge,它会比较栈顶两个int型整数的大小(i和10),不符合就跳转到39行。符合条件就执行下面的语句,然后再第33行iinc指令将变量i+1,然后再执行goto指令跳转到第5行将本地变量表的i加载到操作数栈顶,第6行bipush将整数10加载到操作数栈顶,在第8行if_icmpge又进行比较判断(i和10)。通过这种方式就实现了循环,而new指令是在循环中的,因此每次循环都会创建一个StringBuilder对象。
代码类似下面:

        String str = "";
        for(int i = 0; i < 10; i++) {
            str = (new StringBuilder(str)).append(String.valueOf(i)).toString();
        }
        System.out.println(str);

在循环中做字符串的相加会导致在短时间创建大量的临时对象。因此可以像下面这样写。

        StringBuilder builder = new StringBuilder();
        for(int i = 0; i < 10; i++) {
           builder.append(i);
        }
        System.out.println(builder.toString());

2.3、常量变量之间的+

        final String str1="hello";
        final String str2="world";
        String str3=str1+str2;

这3行代码和2.2的区别在于str1和str2是用final修饰的,此时String str3=str1+str2;这行代码就等同于String str3=“helloworld”。这种final修饰的字符串相加时会出现编译时替换,可以将它看成字面量之间的相加。在这里插入图片描述
因此下面这个问题就很好回答了。

        final String str1="hello";
        final String str2="world";
        String str3=str1+str2;
        String str4="helloworld";
        System.out.println(str3==str4);//true

2.3、变量和字面量之间的+

        String str1="hello";
        String str2=str1+"world";

对于这种会创建一个StringBuilder对象,然后两次调用它的append方法将常量池中的hello和world添加进去。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值