>

深入理解Java中的String

- 编辑:金沙国际平台登录 -

深入理解Java中的String

  

原稿链接:

  java9比方代码:

1   String str1 = "abc";2   String str2 = "abc";3   String str3 = new String("abc");4   String str4 = new String;

      当:  str1 == str2 输出:true    当:str1.equals; 输出:true

      当:  str1 == str3 输出:false    当:str1.equals; 输出:true

      当:  str3 == str4 输出:false    当:str3.equals; 输出:true

        

    涉及到的细节:

     - 能够由此String中intern方法,字符串对象在常量池6月它极其的字符串的引用

         str3.intern() == str4.intern()  输出:true

        str1.intern().equals(str2.intern  输出:true

         str1.intern() ==str1   输出:false

     - String str = new String;创设了多少个目的?

         首先要看常量池里是还是不是有“abc”这一个字符串,假诺有(String str = "abc";出现时则有),则开创贰个,如果未有,则开创四个(贰个在常量池,八个在堆中)。

    == 和 equals区别:

      对于 == :

          成效于基本数据类型的变量,则一向相比其储存的 “值”是或不是等于;

          功效于援引类型的变量,则比较的是所指向的对象的地方;

      对于 equals:  

          equals方法不能效用于基本数据类型的变量;

          若无对Object中equals方法开展重写,则相比较的是引用类型的变量所针对的对象的地方,反之则相比较的是内容;

          

          

一、String类

想要明白七个类,最棒的格局正是看那个类的完成源代码,来看一下String类的源码:

图片 1

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    ........
}

图片 2

从地点能够看出几点:

1)String类是final类,也即意味着String类不能够被一连,而且它的积极分子方法都私下认可为final方法。在Java中,被final修饰的类是不容许被接续的,并且该类中的成员方法都默感到final方法。

2)上边列举出了String类中持有的分子属性,从上边能够见到String类其实是经过char数组来保存字符串的。

下边再持续看String类的片段主意达成:

图片 3

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

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

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = count;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        int off = offset;   /* avoid getfield opcode */

        while (++i < len) {
        if (val[off + i] == oldChar) {
            break;
        }
        }
        if (i < len) {
        char buf[] = new char[len];
        for (int j = 0 ; j < i ; j++) {
            buf[j] = val[off+j];
        }
        while (i < len) {
            char c = val[off + i];
            buf[i] = (c == oldChar) ? newChar : c;
            i++;
        }
        return new String(0, len, buf);
        }
    }
    return this;
}

图片 4

从上边的八个艺术能够看到,无论是sub操、concat照旧replace操作都不是在原有的字符串上扩充的,而是重新生成了一个新的字符串对象。也便是说实行那么些操作后,最原始的字符串并从未被改变。

在此处要永恒铭记在心一点:“String对象一旦被创立就是一直不变的了,对String对象的其余更换都不影响到原对象,相关的别的change操作都会扭转新的对象”

二、字符串常量池

      大家通晓字符串的分红和其他对象分配一样,是急需消耗高昂的日子和空间的,何况字符串我们使用的可怜多。JVM为了抓牢质量和压缩内部存款和储蓄器的支出,在实例化字符串的时候进行了有的优化:行使字符串常量池每当大家创造字符串常量时,JVM会首先检查字符串常量池,如若该字符串已经存在常量池中,那么就一直重临常量池中的实例援引。如若字符串空头支票常量池中,就能够实例化该字符串并且将其置于常量池中。由于String字符串的不可变性我们能够足够必然常量池中毋庸置疑不设有四个同样的字符串(那点对驾驭地方根本)。

Java中的常量池,实际上分为三种形象:静态常量池运作时常量池
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不止蕴含字符串(数字)字面量,还带有类、方法的消息,占用class文件绝大部分上空。
运作时常量池,则是jvm设想机在形成类装载操作后,将class文件中的常量池载入到内部存款和储蓄器中,并保留在方法区中,我们常说的常量池,便是指方法区中的运转时常量池。

来看下边包车型客车程序:

String a = "chenssy";
String b = "chenssy";

a、b和字面上的chenssy都是指向JVM字符串常量池中的"chenssy"对象,他们针对同二个对象。

String c = new String("chenssy");

new关键字一定会时有发生三个对象chenssy(注意那几个chenssy和上面的chenssy区别),同不经常候那几个目的是积存在堆中。所以地点应该发生了三个指标:保存在栈中的c和封存堆中chenssy。但是在Java中常有就不设有五个精光大同小异的字符串对象。故堆中的chenssy应该是引用字符串常量池中chenssy。所以c、chenssy、池chenssy的关联应该是:c--->chenssy--->池chenssy。整个涉及如下:

图片 5

 通过地点的图大家能够足够清晰的认知她们之间的关联。所以大家修改内部存款和储蓄器中的值,他转移的是兼具。

总结:就算如此a、b、c、chenssy是见仁见智的目的,可是从String的内部结构大家是足以驾驭地方的。String c = new String("chenssy");固然c的内容是创建在堆中,不过他的内部value依旧指向JVM常量池的chenssy的value,它构造chenssy时所用的参数照旧是chenssy字符串常量。

上边再来看多少个例证:

例子1:

图片 6

/**
 * 采用字面值的方式赋值
 */
public void test1(){
    String str1="aaa";
    String str2="aaa";
    System.out.println("===========test1============");
    System.out.println(str1==str2);//true 可以看出str1跟str2是指向同一个对象 
}

图片 7

实行上述代码,结果为:true。
深入分析:当试行String str1="aaa"时,JVM首先会去字符串池中追寻是还是不是存在"aaa"那一个指标,借使不设有,则在字符串池中开创"aaa"那几个目的,然后将池中"aaa"那几个目的的引用地址重临给字符串常量str1,那样str1会指向池中"aaa"那么些字符串对象;要是存在,则不创设任何对象,直接将池中"aaa"那些指标的地址重临,赋给字符串常量。当成立字符串对象str2时,字符串池中一度存在"aaa"那么些指标,直接把目的"aaa"的引用地址重临给str2,那样str2指向了池中"aaa"那个指标,也等于说str1和str2指向了同二个对象,因而语句System.out.println(str1 == str2)输出:true。

例子2:

图片 8

/**
 * 采用new关键字新建一个字符串对象
 */
public void test2(){
    String str3=new String("aaa");
    String str4=new String("aaa");
    System.out.println("===========test2============");
    System.out.println(str3==str4);//false 可以看出用new的方式是生成不同的对象 
}

图片 9

 实践上述代码,结果为:false。

分析: 选取new关键字新建二个字符串对象时,JVM首先在字符串池中搜寻有未有"aaa"那么些字符串对象,假使有,则不在池中再去创设"aaa"这几个目的了,直接在堆中开创二个"aaa"字符串对象,然后将堆中的这么些"aaa"对象的地方重返赋给引用str3,那样,str3就对准了堆中创建的这一个"aaa"字符串对象;如果未有,则率先在字符串池中创建一个"aaa"字符串对象,然后再在堆中开创多个"aaa"字符串对象,然后将堆中那些"aaa"字符串对象的地方重临赋给str3引用,那样,str3指向了堆中成立的这一个"aaa"字符串对象。当实行String str4=new String("aaa")时, 因为运用new关键字成立对象时,每便new出来的都是八个新的靶子,也正是说援用str3和str4指向的是八个分裂的对象,因而语句System.out.println(str3 == str4)输出:false。

例子3:

图片 10

/**
 * 编译期确定
 */
public void test3(){
    String s0="helloworld";
    String s1="helloworld";
    String s2="hello"+"world";
    System.out.println("===========test3============");
    System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一个对象 
    System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一个对象 
}

图片 11

实行上述代码,结果为:true、true。

剖判:因为例子中的s0和s1中的"helloworld”都是字符串常量,它们在编译期就被分明了,所以s0==s1为true;而"hello”和"world”也都是字符串常量,当二个字符串由七个字符串常量连接而成时,它协和明显也是字符串常量,所以s2也一致在编写翻译期就被解析为一个字符串常量,所以s2也是常量池中"helloworld”的二个援用。所以大家得出s0==s1==s2。

例子4:

图片 12

/**
 * 编译期无法确定
 */
public void test4(){
    String s0="helloworld"; 
    String s1=new String("helloworld"); 
    String s2="hello" + new String("world"); 
    System.out.println("===========test4============");
    System.out.println( s0==s1 ); //false  
    System.out.println( s0==s2 ); //false 
    System.out.println( s1==s2 ); //false
}

图片 13

执行上述代码,结果为:false、false、false。

浅析:用new String() 创设的字符串不是常量,无法在编译期就明确,所以new String() 创设的字符串不放入常量池中,它们有投机的地方空间。

s0依然常量池中"helloworld”的引用,s1因为不能够在编写翻译期鲜明,所以是运作时创设的新指标"helloworld”的引用,s2因为有后半部分new String(”world”)所以也心有余而力不足在编写翻译期显明,所以也是三个新创制对象"helloworld”的引用。

例子5:

图片 14

/**
 * 继续-编译期无法确定
 */
public void test5(){
    String str1="abc";   
    String str2="def";   
    String str3=str1+str2;
    System.out.println("===========test5============");
    System.out.println(str3=="abcdef"); //false
}

图片 15

实践上述代码,结果为:false。

剖判:因为str3指向堆中的"abcdef"对象,而"abcdef"是字符串池中的对象,所以结果为false。JVM对String str="abc"对象放在常量池中是在编写翻译时做的,而String str3=str1+str2是在运作时刻本事领略的。new对象也是在运行时才做的。而这段代码总共创制了5个目的,字符串池中八个、堆中三个。+运算符会在堆中成立来八个String对象,这多个目的的值分别是"abc"和"def",也便是说从字符串池中复制那八个值,然后在堆中创制多个对象,然后再次创下设目的str3,然后将"abcdef"的堆地址赋给str3。

步骤: 
1)栈中开荒一块中间寄存援用str1,str1指向池中String常量"abc"。 
2)栈中开垦一块中间寄放援用str2,str2指向池中String常量"def"。 
3)栈中开采一块中间存放援引str3。
4)str1 + str2经过StringBuilder的终极一步toString()方法还原二个新的String对象"abcdef",因此堆中开拓一块空间贮存此指标。
5)援引str3指向堆中(str1 + str2)所苏醒的新String对象。 
6)str3指向的指标在堆中,而常量"abcdef"在池中,输出为false。

例子6:

图片 16

/**
 * 编译期优化
 */
public void test6(){
    String s0 = "a1"; 
    String s1 = "a" + 1; 
    System.out.println("===========test6============");
    System.out.println((s0 == s1)); //result = true  
    String s2 = "atrue"; 
    String s3= "a" + "true"; 
    System.out.println((s2 == s3)); //result = true  
    String s4 = "a3.4"; 
    String s5 = "a" + 3.4; 
    System.out.println((s4 == s5)); //result = true
}

图片 17

施行上述代码,结果为:true、true、true。

深入分析:在程序编写翻译期,JVM就将常量字符串的"+"连接优化为总是后的值,拿"a" + 1来讲,经编写翻译器优化后在class中就已经是a1。在编写翻译期其字符串常量的值就规定下来,故上边程序最后的结果都为true。

例子7:

图片 18

/**
 * 编译期无法确定
 */
public void test7(){
    String s0 = "ab"; 
    String s1 = "b"; 
    String s2 = "a" + s1; 
    System.out.println("===========test7============");
    System.out.println((s0 == s2)); //result = false
}

图片 19

推行上述代码,结果为:false。

解析:JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而援引的值在前后相继编写翻译期是不可能显著的,即"a" + s1不能被编写翻译器优化,独有在程序运营期来动态分配并将延续后的新鸿基土地资金财产点赋给s2。所以地方程序的结果也就为false。

例子8:

图片 20

/**
 * 比较字符串常量的“+”和字符串引用的“+”的区别
 */
public void test8(){
    String test="javalanguagespecification";
    String str="java";
    String str1="language";
    String str2="specification";
    System.out.println("===========test8============");
    System.out.println(test == "java" + "language" + "specification");
    System.out.println(test == str + str1 + str2);
}

图片 21

施行上述代码,结果为:true、false。

剖判:为何出现上面包车型地铁结果吧?那是因为,字符串字面量拼接操作是在Java编写翻译器编译时期就施行了,约等于说编写翻译器编写翻译时,直接把"java"、"language"和"specification"那四个字面量举办"+"操作获得多个"javalanguagespecification" 常量,並且直接将那一个常量归入字符串池中,这样压实际是一种优化,将3个字面量合成贰个,幸免了创办多余的字符串对象。而字符串援用的"+"运算是在Java运维时期推行的,即str

  • str2 + str3在程序实行时期才会进展测算,它会在堆内部存款和储蓄器中重新创建八个拼凑后的字符串对象。计算来讲就是:字面量"+"拼接是在编写翻译期间举行的,拼接后的字符串贮存在字符串池中;而字符串援用的"+"拼接运算实在运营时张开的,新创造的字符串寄存在堆中。

对于一贯相加字符串,作用相当高,因为在编写翻译器便鲜明了它的值,也正是说形如"I"+"love"+"java"; 的字符串相加,在编译时期便被优化成了"Ilovejava"。对于直接相加(即包涵字符串援用),形如s1+s2+s3; 成效要比直接相加低,因为在编写翻译器不会对援用变量实行优化。

例子9:

图片 22

/**
 * 编译期确定
 */
public void test9(){
    String s0 = "ab"; 
    final String s1 = "b"; 
    String s2 = "a" + s1;  
    System.out.println("===========test9============");
    System.out.println((s0 == s2)); //result = true
}

图片 23

试行上述代码,结果为:true。

深入分析:和例子7中头一无二差异的是s1字符串加了final修饰,对于final修饰的变量,它在编写翻译时被剖析为常量值的一个地面拷贝存款和储蓄到和谐的常量池中或嵌入到它的字节码流中。所以这时的"a"

  • s1和"a" + "b"效果是平等的。故上面程序的结果为true。

例子10:

图片 24

/**
 * 编译期无法确定
 */
public void test10(){
    String s0 = "ab"; 
    final String s1 = getS1(); 
    String s2 = "a" + s1; 
    System.out.println("===========test10============");
    System.out.println((s0 == s2)); //result = false 

}

private static String getS1() {  
    return "b";   
}

图片 25

施行上述代码,结果为:false。

浅析:这里面即便将s1用final修饰了,不过由于其赋值是通过措施调用再次回到的,那么它的值只好在运作时期鲜明,因而s0和s2指向的不是同二个指标,故上面程序的结果为false。

三、总结

1.String类初叶化后是不可变的(immutable)

String使用private final char value[]来促成字符串的存放,也便是说String对象创立之后,就无法再修改此指标中贮存的字符串内容,正是因为如此,才说String类型是不可变的(immutable)。技术员不能够对已有些不可变对象开展改动。大家丹舟共济也足以创立不可变对象,只要在接口中不提供修改数据的艺术就能够。
唯独,String类对象真正有编写制定字符串的功力,比方replace()。这个编辑成效是通过创办三个新的靶子来落到实处的,并不是对原有对象举行修改。譬喻:

s = s.replace("World", "Universe");

地点对s.replace()的调用将开创三个新的字符串"Hello Universe!",并赶回该指标的引用。通过赋值,引用s将指向该新的字符串。若无别的援用指向原有字符串"Hello World!",原字符串对象将被垃圾回收。

图片 26

2.引用变量与目的

A aa;
以此讲话声惠氏(WYETH)(Nutrilon)个类A的引用变量aa[我们平常称之为句柄],而目的一般通过new创设。所以aa仅仅是叁个援用变量,它不是指标。

3.成立字符串的格局

创立字符串的法子总结起来有两类:

(1)使用""引号创造字符串;

(2)使用new关键字创造字符串。

构成地点例子,总括如下:

(1)单独接纳""引号创设的字符串都以常量,编写翻译期就曾经规定期存款储到String Pool中;

(2)使用new String("")创设的靶子会积存到heap中,是运转期新成立的;

new创造字符串时首先查看池中是否有一样值的字符串,假使有,则拷贝一份到堆中,然后回来堆中的地址;假使池中一贯不,则在堆中创建一份,然后回到堆中的地址(注意,此时不须求从堆中复制到池中,不然,将使得堆中的字符串永恒是池中的子集,导致浪费池的空中)!

(3)使用只含有常量的字符串连接符如"aa" + "aa"创制的也是常量,编写翻译期就会鲜明,已经鲜明期存款款和储蓄到String Pool中;

(4)使用含有变量的字符串连接符如"aa" + s1创办的靶子是运作期才创造的,存款和储蓄在heap中;

4.运用String不必然创立对象

在实践到双引号包涵字符串的说话时,如String a = "123",JVM会先到常量池里寻找,如若部分话重回常量池里的那些实例的援引,不然的话成立贰个新实例并置入常量池里。所以,当大家在选用诸如String str = "abc";的格式定义对象时,总是想当然地感到,成立了String类的靶子str。操心陷阱!对象也许并未被创建!而也许只是指向二个在先早就创建的靶子。唯有由此new()方法才具担保每趟都创设二个新的对象。

5.利用new String,一定创设对象

在施行String a = new String("123")的时候,首先走常量池的路子取到三个实例的引用,然后在堆上创制三个新的String实例,走以下构造函数给value属性赋值,然后把实例援用赋值给a:

图片 27

public String(String original) {
    int size = original.count;
    char[] originalValue = original.value;
    char[] v;
      if (originalValue.length > size) {
         // The array representing the String is bigger than the new
         // String itself.  Perhaps this constructor is being called
         // in order to trim the baggage, so make a copy of the array.
            int off = original.offset;
            v = Arrays.copyOfRange(originalValue, off, off+size);
     } else {
         // The array representing the String is the same
         // size as the String, so no point in making a copy.
        v = originalValue;
     }
    this.offset = 0;
    this.count = size;
    this.value = v;
    }

图片 28

从中大家得以看来,就算是新创立了三个String的实例,不过value是等于常量池中的实例的value,便是说并未有new二个新的字符数组来寄存在"123"。

6.关于String.intern()

intern方法应用:贰个发轫为空的字符串池,它由类String独自维护。当调用 intern方法时,若是池已经包蕴三个十分此String对象的字符串(用equals(oject)方法分明),则赶回池中的字符串。不然,将此String对象增多到池中,并赶回此String对象的引用。

它服从以下准绳:对于随意多少个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

String.intern(); 
再补偿介绍一些:存在于.class文件中的常量池,在运作时期被jvm装载,并且能够扩充。String的intern()方法正是扩张常量池的三个艺术;当三个String实例str调用intern()方法时,java查找常量池中是否有同样unicode的字符串常量,假若有,则赶回其引述,若无,则在常量池中加进二个unicode等于str的字符串并回到它的引用。

图片 29

/**
 * 关于String.intern()
 */
public void test11(){
    String s0 = "kvill"; 
    String s1 = new String("kvill"); 
    String s2 = new String("kvill"); 
    System.out.println("===========test11============");
    System.out.println( s0 == s1 ); //false
    System.out.println( "**********" ); 
    s1.intern(); //虽然执行了s1.intern(),但它的返回值没有赋给s1
    s2 = s2.intern(); //把常量池中"kvill"的引用赋给s2 
    System.out.println( s0 == s1); //flase
    System.out.println( s0 == s1.intern() ); //true//说明s1.intern()返回的是常量池中"kvill"的引用
    System.out.println( s0 == s2 ); //true
}

图片 30

运行结果:false、false、true、true。

7.关于equals和==

(1)对于==,假如效果于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),则向来比较其积累的"值"是不是等于;借使效果于引用类型的变量(String),则相比较的是所针对的目的的地点(正是或不是对准同三个目的)。

(2)equals方法是基类Object中的方法,由此对此有着的持续于Object的类都会有该方法。在Object类中,equals方法是用来相比三个指标的援用是不是等于,就是还是不是针对同一个对象。

(3)对于equals方法,注意:equals方法不可能成效于基本数据类型的变量。若无对equals方法进行重写,则相比的是引用类型的变量所针对的对象的地方;而String类对equals方法开展了重写,用来相比指向的字符串对象所蕴藏的字符串是还是不是等于。其余的局地类诸如Double,Date,Integer等,都对equals方法进行了重写用来相比较指向的靶子所蕴藏的内容是或不是等于。

图片 31

/**
 * 关于equals和==
 */
public void test12(){
    String s1="hello";
    String s2="hello";
    String s3=new String("hello");
    System.out.println("===========test12============");
    System.out.println( s1 == s2); //true,表示s1和s2指向同一对象,它们都指向常量池中的"hello"对象
    //flase,表示s1和s3的地址不同,即它们分别指向的是不同的对象,s1指向常量池中的地址,s3指向堆中的地址
    System.out.println( s1 == s3); 
    System.out.println( s1.equals(s3)); //true,表示s1和s3所指向对象的内容相同
}

图片 32

8.String相关的+:

String中的 + 常用于字符串的连日。看上边叁个简单易行的例证:

图片 33

/**
 * String相关的+
 */
public void test13(){
    String a = "aa";
    String b = "bb";
    String c = "xx" + "yy " + a + "zz" + "mm" + b;
    System.out.println("===========test13============");
    System.out.println(c);
}

图片 34

编译运转后,首要字节码部分如下:

图片 35

public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 5 L0
    LDC "aa"
    ASTORE 1
   L1
    LINENUMBER 6 L1
    LDC "bb"
    ASTORE 2
   L2
    LINENUMBER 7 L2
    NEW java/lang/StringBuilder
    DUP
    LDC "xxyy "
    INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "zz"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "mm"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 3
   L3
    LINENUMBER 8 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 3
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 9 L4
    RETURN
   L5
    LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
    LOCALVARIABLE a Ljava/lang/String; L1 L5 1
    LOCALVARIABLE b Ljava/lang/String; L2 L5 2
    LOCALVARIABLE c Ljava/lang/String; L3 L5 3
    MAXSTACK = 3
    MAXLOCALS = 4
}

图片 36

显而易见,通过字节码大家得以得出如下几点结论:
(1).String中动用 + 字符串连接符实行字符串连接时,连接操作最开端时倘诺都以字符串常量,编译后将尽恐怕多的一向将字符串常量连接起来,形成新的字符串常量参预后续连接(通过反编译工具jd-gui也能够方便的直白观看);

(2).接下来的字符串连接是从左向右依次实行,对于分歧的字符串,首先以最左侧包车型大巴字符串为参数创设StringBuilder对象,然后依次对右侧举办append操作,最后将StringBuilder对象通过toString()方法调换来String对象(注意:中间的多少个字符串常量不会自动拼接)。

也正是说String c = "xx" + "yy " + a + "zz" + "mm" + b; 实质上的落到实处进度是: String c = new StringBuilder("xxyy ").append(a).append("zz").append("mm").append(b).toString();

通过得出结论:当使用+实行几个字符串连接时,实际上是发出了三个StringBuilder对象和三个String对象。

9.String的不可变性导致字符串变量使用+号的代价:

String s = "a" + "b" + "c"; 
String s1  =  "a"; 
String s2  =  "b"; 
String s3  =  "c"; 
String s4  =  s1  +  s2  +  s3;

深入分析:变量s的始建等价于 String s = "abc"; 由上边例子可见编写翻译器实行了优化,这里只成立了三个目的。由地点的例子也得以知道s4不能在编译期进行优化,其目的创造也等于:

StringBuilder temp = new StringBuilder();   
temp.append(a).append(b).append(c);   
String s = temp.toString();

由地点的剖释结果,可就一蹴即至揣摸出String 接纳连接运算符(+)成效低下原因分析,形如那样的代码:

图片 37

public class Test {
    public static void main(String args[]) {
        String s = null;
        for(int i = 0; i < 100; i++) {
            s += "a";
        }
    }
}

图片 38

每做三次 + 就爆发个StringBuilder对象,然后append后就扔掉。下一次循环再达到时再次产生个StringBuilder对象,然后 append 字符串,如此循环直至甘休。 假使我们一直运用 StringBuilder 对象开展 append 的话,大家能够节约 N - 1 次成立和销毁对象的小时。所以对于在循环中要进行字符串连接的施用,一般都以用StringBuffer或StringBulider对象来张开append操作。

10.String、StringBuffer、StringBuilder的区别

(1)可变与不可变:String是不足变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数主管度可变)。

(2)是或不是十二线程安全:String中的对象是不可变的,也就足以知道为常量,鲜明线程安全。StringBuffer 与 StringBuilder 中的方法和职能完全部是等价的,只是StringBuffer 中的方法比较多接纳了synchronized 关键字张开修饰,因而是线程安全的,而 StringBuilder 未有那些修饰,可以被认为是非线程安全的。

(3)String、StringBuilder、StringBuffer三者的实施功用:
StringBuilder > StringBuffer > String 当然那个是相对的,不断定在具有景况下都以那般。比如String str = "hello"+ "world"的频率就比 StringBuilder st  = new StringBuilder().append("hello").append("world")要高。因而,那多少个类是各有利弊,应当依照不一样的场合来进行分选选用:
当字符串相加操作依然转移很少的境况下,提议使用 String str="hello"这种格局;
当字符串相加操作很多的事态下,建议利用StringBuilder,如若选择了二十三十二线程,则动用StringBuffer。

11.String中的final用法和透亮

final StringBuffer a = new StringBuffer("111");
final StringBuffer b = new StringBuffer("222");
a=b;//此句编译不通过

final StringBuffer a = new StringBuffer("111");
a.append("222");//编译通过

可见,final只对引用的"值"(即内部存款和储蓄器地址)有效,它迫使引用只可以指向初阶指向的不得了指标,改换它的针对性会促成编写翻译期错误。至于它所指向的靶子的变动,final是不担负的。

12.关于String str = new String("abc")创建了有个别个对象?

其一标题在数不胜数图书上都有谈起比如《Java程序员面试宝典》,包含广大境内大厂商笔试面试题都会遇上,超越五成网络流传的以及一些面试书籍上都说是2个对象,这种说法是以管窥天的。

先是必得弄明白创立对象的意义,成立是如几时候创设的?这段代码在运营期间会创设2个对象么?确实无疑不容许,用javap -c反编写翻译就可以获取JVM实行的字节码内容:

图片 39

很明朗,new只调用了二遍,约等于说只成立了贰个目的。而那道标题令人歪曲的地方正是此处,这段代码在运维时期真的只开创了三个对象,即在堆上创造了"abc"对象。而为什么大家都在说是2个对象呢,这当中要弄清四个概念,该段代码推行进程和类的加载进度是有分其余。在类加载的长河中,确实在运转时常量池中创立了二个"abc"对象,而在代码实行进度中的确只成立了二个String对象。
所以,这么些难点倘若换来 String str = new String("abc")涉及到多少个String对象?合理的讲明是2个。
民用以为在面试的时候要是蒙受那几个主题素材,能够向面试官询问通晓”是这段代码实行进度中成立了不怎么个目的依旧提到到稍微个对象“再依据实际的来张开回复。

13.字符串池的优劣点:
字符串池的帮助和益处正是幸免了一样内容的字符串的创造,节省了内部存款和储蓄器,省去了创制同样字符串的岁月,同不常候提高了品质;另一方面,字符串池的破绽正是就义了JVM在常量池中遍历对象所须要的时间,可是其时间开支相比来说比异常的低。

四、综合实例

图片 40

package com.spring.test;

public class StringTest {
    public static void main(String[] args) {  
        /** 
         * 情景一:字符串池 
          * JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象; 
         * 并且可以被共享使用,因此它提高了效率。 
         * 由于String类是final的,它的值一经创建就不可改变。 
         * 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。  
         */  
        String s1 = "abc";     
        //↑ 在字符串池创建了一个对象  
        String s2 = "abc";     
        //↑ 字符串pool已经存在对象“abc”(共享),所以创建0个对象,累计创建一个对象  
        System.out.println("s1 == s2 : "+(s1==s2));    
        //↑ true 指向同一个对象,  
        System.out.println("s1.equals(s2) : " + (s1.equals(s2)));    
        //↑ true  值相等  
        //↑------------------------------------------------------over  
        /** 
         * 情景二:关于new String("") 
         *  
         */  
        String s3 = new String("abc");  
        //↑ 创建了两个对象,一个存放在字符串池中,一个存在与堆区中;  
        //↑ 还有一个对象引用s3存放在栈中  
        String s4 = new String("abc");  
        //↑ 字符串池中已经存在“abc”对象,所以只在堆中创建了一个对象  
        System.out.println("s3 == s4 : "+(s3==s4));  
        //↑false   s3和s4栈区的地址不同,指向堆区的不同地址;  
        System.out.println("s3.equals(s4) : "+(s3.equals(s4)));  
        //↑true  s3和s4的值相同  
        System.out.println("s1 == s3 : "+(s1==s3));  
        //↑false 存放的地区多不同,一个栈区,一个堆区  
        System.out.println("s1.equals(s3) : "+(s1.equals(s3)));  
        //↑true  值相同  
        //↑------------------------------------------------------over  
        /** 
         * 情景三:  
         * 由于常量的值在编译的时候就被确定(优化)了。 
         * 在这里,"ab"和"cd"都是常量,因此变量str3的值在编译时就可以确定。 
         * 这行代码编译后的效果等同于: String str3 = "abcd"; 
         */  
        String str1 = "ab" + "cd";  //1个对象  
        String str11 = "abcd";   
        System.out.println("str1 = str11 : "+ (str1 == str11));  
        //↑------------------------------------------------------over  
        /** 
         * 情景四:  
         * 局部变量str2,str3存储的是存储两个拘留字符串对象(intern字符串对象)的地址。 
         *  
         * 第三行代码原理(str2+str3): 
         * 运行期JVM首先会在堆中创建一个StringBuilder类, 
         * 同时用str2指向的拘留字符串对象完成初始化, 
         * 然后调用append方法完成对str3所指向的拘留字符串的合并, 
         * 接着调用StringBuilder的toString()方法在堆中创建一个String对象, 
         * 最后将刚生成的String对象的堆地址存放在局部变量str3中。 
         *  
         * 而str5存储的是字符串池中"abcd"所对应的拘留字符串对象的地址。 
         * str4与str5地址当然不一样了。 
         *  
         * 内存中实际上有五个字符串对象: 
         *       三个拘留字符串对象、一个String对象和一个StringBuilder对象。 
         */  
        String str2 = "ab";  //1个对象  
        String str3 = "cd";  //1个对象                                         
        String str4 = str2+str3;                                        
        String str5 = "abcd";    
        System.out.println("str4 = str5 : " + (str4==str5)); // false  
        //↑------------------------------------------------------over  
        /** 
         * 情景五: 
         *  JAVA编译器对string + 基本类型/常量 是当成常量表达式直接求值来优化的。 
         *  运行期的两个string相加,会产生新的对象的,存储在堆(heap)中 
         */  
        String str6 = "b";  
        String str7 = "a" + str6;  
        String str67 = "ab";  
        System.out.println("str7 = str67 : "+ (str7 == str67));  
        //↑str6为变量,在运行期才会被解析。  
        final String str8 = "b";  
        String str9 = "a" + str8;  
        String str89 = "ab";  
        System.out.println("str9 = str89 : "+ (str9 == str89));  
        //↑str8为常量变量,编译期会被优化  
        //↑------------------------------------------------------over  
    }
}

图片 41

运行结果:

s1 == s2 : true
s1.equals(s2) : true
s3 == s4 : false
s3.equals(s4) : true
s1 == s3 : false
s1.equals(s3) : true
str1 = str11 : true
str4 = str5 : false
str7 = str67 : false
str9 = str89 : true

本文由编程发布,转载请注明来源:深入理解Java中的String