在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。
String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。
String 对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
字符串常量池
字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。
每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。
String c = "xxx";
String d = "xxx";
System.out.println(c == d);
/**
* 结果为true
* 当执行String c = "xxx" 时,JVM首先会去字符串池中查找是否存在"xxx"这个对象,
* 如果不存在,则在字符串池中创建"xxx"这个对象,然后将池中"xxx"这个对象的引用地址返回给字符串常量c
* 如果存在,则不创建任何对象,直接将池中"xxx"这个对象的地址返回,赋给字符串常量。
* 对于d 同理
* 也就是说c 与d 指向了同一个对象,所以输出为true
*/
String str1=new String("aaa");
String str2=new String("aaa");
System.out.println(str1==str2);
/**
* 结果为false
* JVM首先在字符串池中查找有没有"aaa"这个字符串对象,
* 如果有,则不在池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回赋给引用str1,
* 如果没有,则首先在字符串池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,然后将堆中这个"aaa"字符串对象的地址返回赋给str1引用
* 对于str2 同理
*
* 因为采用new关键字创建对象时,每次new出来的都是一个新的对象
*
* 也就是说引用str1和str2指向的是两个不同的对象,因此语句System.out.println(str1 == str2)输出:false。
*/
编译期确定
感觉就是一个变量与常量的问题
String s0="helloworld";
String s1="helloworld";
String s2="hello"+"world";
System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一个对象
System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一个对象
/**
* s0和s1中的"helloworld”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;
* 而"hello”和"world”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,
* 所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中"helloworld”的一个引用。
*/
编译期无法确定
String s0="helloworld";
String s1=new String("helloworld");
String s2="hello" + new String("world");
System.out.println( s0==s1 ); //false
System.out.println( s0==s2 ); //false
System.out.println( s1==s2 ); //false
/**
* s0还是常量池中"helloworld”的引用,s1因为无法在编译期确定,所以是运行时创建的新对象"helloworld”的引用,
* s2因为有后半部分new String(”world”)所以也无法在编译期确定,所以也是一个新创建对象"helloworld”的引用。
*/
String s0 = "ab";
String s1 = "b";
String s2 = "a" + s1;
System.out.println((s0 == s2)); //result = false
/**
* JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,
* 即"a" + s1无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给s2。
* 所以上面程序的结果也就为false。
*/
String str1="abc";
String str2="def";
String str3=str1+str2;
System.out.println(str3=="abcdef"); //false
/**
* 因为str3指向堆中的"abcdef"对象,而"abcdef"是字符串池中的对象,所以结果为false。
* JVM对String str="abc"对象放在常量池中是在编译时做的,而String str3=str1+str2是在运行时刻才能知道的。new对象也是在运行时才做的。
*/
String s0 = "ab";
final String s1 = "b";
String s2 = "a" + s1;
System.out.println((s0 == s2)); //result = true
/**
* 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。
* 所以此时的"a" + s1和"a" + "b"效果是一样的。故上面程序的结果为true。
*/
String s0 = "ab";
final String s1 = getS1();
String s2 = "a" + s1;
System.out.println((s0 == s2)); //result = false
private static String getS1() {
return "b";
}
/**
* 这里面虽然将s1用final修饰了,但是由于其赋值是通过方法调用返回的,
* 那么它的值只能在运行期间确定,因此s0和s2指向的不是同一个对象,故上面程序的结果为false。
*/