String、new String()的知识点梳理

前言

String str="1"和new String("1")的区别,其实是个老生常谈的问题,理解起来也挺简单,但是许多人还是很模糊。今天跟新人交流时发现他们对这块的认识也存在偏差,遂在这里系统整理一下String的相关问题。

在叙述问题先,我们要先明白赋值和引用。因为这点对我们理解下边的代码很重要,不然很容易晕。参考这篇文章:https://blog.csdn.net/yz930618/article/details/76278997

问题示例

首先把String涉及到的常见问题用java方法一一列举出来。

package com.ming.test;

public class StringTest {
	StringTest() {
		
	}
	private StringTest(String ss) {
		
	}
	public void test() {
		String str="123";
	}
	public void test1() {
		String str=new String("123");
	}
	public void test2() {
		String str="1"+"2"+"3";
	}
	public void test3() {
		String s="1";
		String t="2";
		String r="3";
		String str=s+t+r;
	}
	public void test4() {
		String str=new StringBuffer().append("1").append("2").append("3").toString();
	}
	public void test5() {
		String str=new StringBuilder().append("1").append("2").append("3").toString();
	}
	public void test6() {
		String s1="123";
		String s2="123";
		System.out.println(s1==s2);
		System.out.println(s1.equals(s2));
	}
	public void test7() {
		new StringTest("123");
	}
	public void test8() {
		String str="123";
		str.intern();
		String str1=new String("123");
		String str2=new String("123");
	}
}

查看java字节码文件

首先通过javac 将java文件编译成.class文件,然后通过命令:

javap -v 字节码.class

 查看.class文件内容,对命令不熟的自己百度下,或者通过java -help查看,字节码内容如下如下,大致分为:基本信息、常量池、构造器、方法内容

public class com.ming.test.StringTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #22.#43        // java/lang/Object."<init>":()V
   #2 = String             #44            // 123
   #3 = Class              #45            // java/lang/String
   #4 = Methodref          #3.#46         // java/lang/String."<init>":(Ljava/lang/String;)V
   #5 = String             #47            // 1
   #6 = String             #48            // 2
   #7 = String             #49            // 3
   #8 = Class              #50            // java/lang/StringBuilder
   #9 = Methodref          #8.#43         // java/lang/StringBuilder."<init>":()V
  #10 = Methodref          #8.#51         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #11 = Methodref          #8.#52         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #12 = Class              #53            // java/lang/StringBuffer
  #13 = Methodref          #12.#43        // java/lang/StringBuffer."<init>":()V
  #14 = Methodref          #12.#54        // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
  #15 = Methodref          #12.#52        // java/lang/StringBuffer.toString:()Ljava/lang/String;
  #16 = Fieldref           #55.#56        // java/lang/System.out:Ljava/io/PrintStream;
  #17 = Methodref          #57.#58        // java/io/PrintStream.println:(Z)V
  #18 = Methodref          #3.#59         // java/lang/String.equals:(Ljava/lang/Object;)Z
  #19 = Class              #60            // com/ming/test/StringTest
  #20 = Methodref          #19.#46        // com/ming/test/StringTest."<init>":(Ljava/lang/String;)V
  #21 = Methodref          #3.#61         // java/lang/String.intern:()Ljava/lang/String;
  #22 = Class              #62            // java/lang/Object
  #23 = Utf8               <init>
  #24 = Utf8               ()V
  #25 = Utf8               Code
  #26 = Utf8               LineNumberTable
  #27 = Utf8               (Ljava/lang/String;)V
  #28 = Utf8               test
  #29 = Utf8               test1
  #30 = Utf8               test2
  #31 = Utf8               test3
  #32 = Utf8               test4
  #33 = Utf8               test5
  #34 = Utf8               test6
  #35 = Utf8               StackMapTable
  #36 = Class              #60            // com/ming/test/StringTest
  #37 = Class              #45            // java/lang/String
  #38 = Class              #63            // java/io/PrintStream
  #39 = Utf8               test7
  #40 = Utf8               test8
  #41 = Utf8               SourceFile
  #42 = Utf8               StringTest.java
  #43 = NameAndType        #23:#24        // "<init>":()V
  #44 = Utf8               123
  #45 = Utf8               java/lang/String
  #46 = NameAndType        #23:#27        // "<init>":(Ljava/lang/String;)V
  #47 = Utf8               1
  #48 = Utf8               2
  #49 = Utf8               3
  #50 = Utf8               java/lang/StringBuilder
  #51 = NameAndType        #64:#65        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #52 = NameAndType        #66:#67        // toString:()Ljava/lang/String;
  #53 = Utf8               java/lang/StringBuffer
  #54 = NameAndType        #64:#68        // append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
  #55 = Class              #69            // java/lang/System
  #56 = NameAndType        #70:#71        // out:Ljava/io/PrintStream;
  #57 = Class              #63            // java/io/PrintStream
  #58 = NameAndType        #72:#73        // println:(Z)V
  #59 = NameAndType        #74:#75        // equals:(Ljava/lang/Object;)Z
  #60 = Utf8               com/ming/test/StringTest
  #61 = NameAndType        #76:#67        // intern:()Ljava/lang/String;
  #62 = Utf8               java/lang/Object
  #63 = Utf8               java/io/PrintStream
  #64 = Utf8               append
  #65 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #66 = Utf8               toString
  #67 = Utf8               ()Ljava/lang/String;
  #68 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuffer;
  #69 = Utf8               java/lang/System
  #70 = Utf8               out
  #71 = Utf8               Ljava/io/PrintStream;
  #72 = Utf8               println
  #73 = Utf8               (Z)V
  #74 = Utf8               equals
  #75 = Utf8               (Ljava/lang/Object;)Z
  #76 = Utf8               intern
{
  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String 123
         2: astore_1
         3: return
      LineNumberTable:
        line 11: 0
        line 12: 3

  public void test1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #3                  // class java/lang/String
         3: dup
         4: ldc           #2                  // String 123
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: astore_1
        10: return
      LineNumberTable:
        line 14: 0
        line 15: 10

  public void test2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String 123
         2: astore_1
         3: return
      LineNumberTable:
        line 17: 0
        line 18: 3

  public void test3();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=1
         0: ldc           #5                  // String 1
         2: astore_1
         3: ldc           #6                  // String 2
         5: astore_2
         6: ldc           #7                  // String 3
         8: astore_3
         9: new           #8                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #9                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: aload_3
        25: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        28: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        31: astore        4
        33: return
      LineNumberTable:
        line 20: 0
        line 21: 3
        line 22: 6
        line 23: 9
        line 24: 33

  public void test4();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #12                 // class java/lang/StringBuffer
         3: dup
         4: invokespecial #13                 // Method java/lang/StringBuffer."<init>":()V
         7: ldc           #5                  // String 1
         9: invokevirtual #14                 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        12: ldc           #6                  // String 2
        14: invokevirtual #14                 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        17: ldc           #7                  // String 3
        19: invokevirtual #14                 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
        22: invokevirtual #15                 // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
        25: astore_1
        26: return
      LineNumberTable:
        line 26: 0
        line 27: 26

  public void test5();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #8                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #9                  // Method java/lang/StringBuilder."<init>":()V
         7: ldc           #5                  // String 1
         9: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        12: ldc           #6                  // String 2
        14: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        17: ldc           #7                  // String 3
        19: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        22: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        25: astore_1
        26: return
      LineNumberTable:
        line 29: 0
        line 30: 26

  public void test6();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: ldc           #2                  // String 123
         2: astore_1
         3: ldc           #2                  // String 123
         5: astore_2
         6: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: aload_2
        11: if_acmpne     18
        14: iconst_1
        15: goto          19
        18: iconst_0
        19: invokevirtual #17                 // Method java/io/PrintStream.println:(Z)V
        22: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
        25: aload_1
        26: aload_2
        27: invokevirtual #18                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
        30: invokevirtual #17                 // Method java/io/PrintStream.println:(Z)V
        33: return
      LineNumberTable:
        line 32: 0
        line 33: 3
        line 34: 6
        line 35: 22
        line 36: 33
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 18
          locals = [ class com/ming/test/StringTest, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class com/ming/test/StringTest, class java/lang/String, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]

  public void test7();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #19                 // class com/ming/test/StringTest
         3: dup
         4: ldc           #2                  // String 123
         6: invokespecial #20                 // Method "<init>":(Ljava/lang/String;)V
         9: pop
        10: return
      LineNumberTable:
        line 38: 0
        line 39: 10

  public void test8();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=4, args_size=1
         0: ldc           #2                  // String 123
         2: astore_1
         3: aload_1
         4: invokevirtual #21                 // Method java/lang/String.intern:()Ljava/lang/String;
         7: pop
         8: new           #3                  // class java/lang/String
        11: dup
        12: ldc           #2                  // String 123
        14: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        17: astore_2
        18: new           #3                  // class java/lang/String
        21: dup
        22: ldc           #2                  // String 123
        24: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        27: astore_3
        28: return
      LineNumberTable:
        line 41: 0
        line 42: 3
        line 43: 8
        line 44: 18
        line 45: 28
}

对于上边的内容大家可能一脸懵逼。其实这些都是jvm的指令,网上一搜一大把,我随便搜了一篇文章的。具体的指令集可以参看这篇文章传送门。那么我把上边需要用到的指令贴一下。

  • ldc:代表将int、float或String型常量值从常量池中推送至栈顶。
  • astore:代表将栈顶数值(objectref)存入当前 frame的局部变量数组中指定下标(index)处的变量中,栈顶数值出栈。

  • new:代表创建一个对象,并且其引用进栈;

  • dup:代表复制栈顶数值,并且复制值进栈;

  • invokespecial:代表调用超类构造方法、实例初始化方法、私有方法;

  • aload:代表当前frame的局部变量数组中下标为index的引用型局部变量进栈;

常量池就是存放一些类基本信息和类中的常量,代码中会调用常量池的内容,我们要理解方法是如何运作的,利用一个栈和一个本地变量区,来做运算的,具体分析见下边代码。

 完成以上概念的介绍后,我们就解读下字节码的内容,首先看下test方法做了什么

//原方法
public void test() {
		String str="123";
	}
//对应的jvm指令
public void test();
    Code:
        //将常量池中的123(#2代表去常量池中找#2的常量)推至栈顶
       0: ldc           #2                  // String 123
        //将栈顶数值(123)存入当前frame的局部变量数组中指定下标(1)处的变量中,栈顶数值出栈
       2: astore_1
        //当前方法返回void
       3: return

其实上边的操作就是把常量池中的123地址赋值给当前方法栈中的临时变量str。至于常量池中的123,是在类加载的时候就已经创建了。系统会先看下常量池中是否有123这个字符串,没有的话就会新增一个123字符串,有的话就不会创建了。

看下test1方法做了什么

  //原方法  
  public void test1() {
		String str=new String("123");
	}

  //jvm指令
  public void test1();
	    Code:
            //在堆中创建一个对象,并将其堆地址存入栈顶
	       0: new           #3                  // class java/lang/String
            //复制栈顶堆地址,并且复制值进栈,就是说当前栈中有2个一样的堆地址值
	       3: dup
            //将String型常量值123从常量池中推送至栈顶
	       4: ldc           #2                  // String 123
            //调用超类构造方法、实例初始化方法、私有方法,并取栈中一个堆地址作为该对象的this
	       6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
            //将栈顶数值(123)存入当前frame的局部变量数组中指定下标(1)处的变量中,栈顶数值出栈
	       9: astore_1
	      10: return

概括一下就是:new String("123")不管常量池中有没有123这个字符池,系统都会在堆内存中创建一个String对象,并且String对象的成员变量char[] value指向"123"常量池对象的char[] value(参考String构造方法),这也是为什么new String("123")这个对象的值是123,常量池中如果没有123字符串的话还会在常量池中新增123字符串常量。

这里要明确一点test1方法中str的值是123,而非string对象的堆地址。
由test方法和test1方法,我们可以知道,使用new String()创建字符串会比常规的写法多new一个对象。

test2方法的字节码内容和test方法的字节码内容是一样的。
由此可知String str="1"+"2"+"3";和String str="123";效果是一样的。

我们接着看下test3方法的字节码内容会发现String str=s+t+r;相当于系统给我们创建一个StringBuilder对象调用了三次append方法。和test5的方法是一样的。相比之下test3多创建了1、2、3三个字符串常量在常量池中。

我们接着看下test4方法会发现其实跟test5方法是一样的,只不过test4创建的是StringBuffer,test5创建的是StringBuilder,我们知道StringBuffer是线程安全的,所以增加了线程安全的处理,常规下会比StringBuilder慢一点。如果多线程操作的话,使用StringBuffer。

String的intern方法

  * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>

intern是个native方法。我们可以看下方法的注释,大致意思就是返回该字符串常量池中的地址,如果常量池中没有这个字符串就会新增一个到常量池中。

举个例子,String str=new String("123"),通过上边分析我们知道,执行完这句话,常量池中会新增123的常量,java堆中会新增一个对象,值为“123”.那么str.intern()就会返回字符串“123”在常量池中的地址。而不会返回堆中的地址。

看到这不知道大家对于这块有没有一个新的认识。看下下边这个代码输出的结果是什么?

String str1=new String("123");
String str2=new String("123");
System.out.println(str1==str2);

答案是false,我们知道==是判断两个对象的地址是否相等。
上边我们说过new String("123")返回的地址是java堆中的对象地址。而且每次调用这句话都会在堆中新创建一个对象并初始化值“123”.虽然str1和str2的代码是一样的,但是是创建了2个不同的对象,所以它两的地址是不一样的。

但是如果str1.intern()和str2.intern()的地址是一样的。因为他们的值是一样的,都会返回字符串常量池中“123”的地址。

equals和==

equals方法是object对象的方法,如果类没有重写这个方法的话都是默认调用的object的equals方法。默认的equals方法如下:

  public boolean equals(Object obj) {
        return (this == obj);
    }

说明默认的equals方法和==是没有区别的。 

但String类是重写了equals方法的。方法如下:

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

首先会判断是否地址相等,如果地址相等的话,直接返回true,如果地址不相等的话,在判断值是否相等,如果值相等的话返回true。概括一下就是String类的equals方法是判断值相等。所以当我们用字符串判断相等时,要考虑是什么用途,再考虑用哪种方法。

String str1=new String("123");
String str2=new String("123");
System.out.println(str1==str2);

通过上边的介绍,我们不难猜想出,str1.equals(str2) 是true,因为他们值相等。都是“123”。但他们地址是不相等的。

以上就是对字符串相关的小知识点总结。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值