Cry ! 让人头疼的String

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/J080624/article/details/82316464

【1】String为什么是不可变的

① 只有当字符串是不可变的,字符串池才有可能实现

字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。

但如果字符串是可变的,那么String interning将不能实现(译者注:String intern是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。


② 如果字符串是可变的,那么会引起很严重的安全问题

譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。


③ 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享

这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。


④ 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。

譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。


⑤ 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算

这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。


String中的final用法和理解:

final只对引用的”值”(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。

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

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

【2】Some Sick Statement

First

  public static void main(String[] args){

        String str1 = "abc";
        String str2 = new String("abc");
        System.out.println(str1==str2);//false


        String str3 = new String("abc") + new String("abc");
        String str4 = "abcabc";
        System.out.println(str3==str4);//false

        String str7 = str1+str2;
        System.out.println(str3==str7);//false
        System.out.println(str4==str7);//false

        String str8 = "abc"+"abc";
        System.out.println(str3==str8);//false
        System.out.println(str4==str8);//true

    }

JVM对于字符串常量的”+”号连接,将程序编译期,JVM就将常量字符串的”+”连接优化为连接后的值,拿"abc"+"abc"来说,经编译器优化后在class中就已经是”abcabc”。在编译期其字符串常量的值就确定下来,故str4==str8为true

然后对象引用的拼接,如String str7 = str1+str2;将会新建一个String对象,故str3==str7为false


继续往下看。

        String a = "ab";  
        String b = "b";  
        String c = "a" + b;  
        System.out.println((a == c)); //result = false  

JVM对于字符串引用,由于在字符串的”+”连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即”a” + b无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。


        String a = "ab";
        final String b = "b";
        String c = "a" + b;
        System.out.println((a == c)); //result = true   

和上面唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的”a” + b和”a” + “b”效果是一样的。故上面程序的结果为true。


Second

public static void main(String[] args){

        String str3 = new String("abc") + new String("abc");
        str3.intern();
        String str4=str3.intern();
        String str7= "abcabc";
        System.out.println(str3==str4);//true
        System.out.println(str3==str7);//true
        System.out.println(str4==str7);//true

        // 注意,此时str5.intern()时机不同
        String str5 = new String("abcd") + new String("abcd");
        String str6 = "abcdabcd";
        String str8=str5.intern();
        System.out.println(str5==str6);//false
        System.out.println(str5==str8);//false
        System.out.println(str8==str6);//true


    }

【3】字符串与数组

示例一:

public class A {
    public final String tempString="world";
    //这里可以把final去掉,结果等同!!

    public final char[] charArray="Hello".toCharArray();

    public char[] getCharArray() {
        return charArray;
    }
    public String getTempString() {
        return tempString;
    }
}

测试类如下:

public class TestA {
    public static void main(String[] args) {
        A a1=new  A();
        A a2=new A();

        System.out.println(a1.charArray==a2.charArray);
        System.out.println(a1.tempString==a2.tempString);
    }
}

输出结果如下:

false
true

① 字符串为什么会输出true

  • 一个Class字节码文件只有一个常量池,常量池被所有线程共享。

  • 在常量池中,字符串被存储为一个字符序列,每个字符序列都对应一个String对象,该对象保存在堆中。所以也就是说为什么String temp=”xxx”;能够当成一个对象使用!!

  • 如果多个线程去访问A类中的String字符串,每次都会到常量区中去找该字符序列的引用。

  • 所以访问A类被创建的两个A类型对象的String字符串对比会输出true。


② 数组为什么是false

声明(不管是通过new还是通过直接写一个数组)一个数组其实在Java中就等同创建了一个对象,即每次创建类的对象都会自动创建一个新的数组空间。

其中要注意的是:常量池中存储字符数组只是存储的是每个字符或者字符串。

为了证明每次获取的final数组地址不一样,并且数组中的字符都会存储在常量池中,我们需要参考另外一个代码。

示例二:

public class A {
    public String tempString="world";
    public final String tempStringArray[]={"Fire","Lang"};
    public final char[] charArray={'h','e','l','l','o'};
    public Character charx='l';

    public char[] getCharArray() {
        return charArray;
    }
    public String getTempString() {
        return tempString;
    }
    public String[] getTempStringArray() {
        return tempStringArray;
    }
    public Character getCharx() {
        return charx;
    }
}

测试类如下:

public class TestA {

    public static void main(String[] args) {
        A a1=new  A();
        A a2=new A();
        System.out.println(a1.tempString==a2.tempString);
        System.out.println(a1.tempStringArray==a2.tempStringArray);//看这里
        System.out.println("#####################");//看这里
        System.out.println(a1.tempStringArray[0]==a2.tempStringArray[0]);
        System.out.println(a1.tempStringArray[0]=="Fire");
        System.out.println("#####################");
        System.out.println(a1.charArray==a2.charArray);
        System.out.println(a1.charx==a2.charx);
    }
}

输出结果如下:

true
false
#####################
true
true
#####################
false
true

【4】字符串与常量池

String s = new String( "xyz" ); 在运行时涉及 几个String实例?

两个,一个是字符串字面量”xyz”所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(String)创建并初始化的、内容与”xyz”相同的实例。

猜你喜欢

转载自blog.csdn.net/J080624/article/details/82316464