1.基本概念 1)创建字符串的方式 1.使用""创建字符串 2.使用new关键字创建字符串 总结: (1)单独使用""引号创建的字符串都是常量,编译期就已经确定存储到String Pool中;
(2)使用new String("")创建的对象会存储到heap中,是运行期新创建的;
new创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;
如果池中没有,则在堆中创建一份,然后返回堆中的地址(注意,此时不需要从堆中复制到池中,
否则,将使得堆中的字符串永远是池中的子集,导致浪费池的空间)!
(3)使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,已经确定存储到String Pool中;
(4)使用包含变量的字符串连接符如"aa" + s1创建的对象是运行期才创建的,存储在heap中;
2)在执行到双引号包含字符串的语句时,如String a = "123",JVM会先到常量池里查找, 如果有的话返回常量池里的这个实例的引用,否则的话创建一个新实例并置入常量池里。 所以,当我们在使用诸如String str = "abc";的格式定义对象时,总是想当然地认为, 创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。 3)使用new String,一定创建对象 4)关于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方法进行了重写用来比较指向的对象所存储的内容是否相等。
2.测试验证
/* * 1. 采用字面值的方式赋值 * 因为"123"已经存在在字符串常量池里了,JVM会把该引用直接指向b,因此a与b相同, * 同时也说明字符串常量池中一定不存在2个相同的字符串。 * * */ String a = "123"; String b = "123"; System.out.println(a == b); //true
/* * 2.采用new关键字新建一个字符串对象 * 1)首先在字符串池中创建一个"123"字符串对象,然后再在堆中创建一个"123"字符串对象, * 然后将堆中这个"123"字符串对象的地址返回赋给a引用,这样,a指向了堆中创建 * 的这个"123"字符串对象。 * 2)当执行String b=new String("123")时, 因为采用new关键字创建对象时, * 每次new出来的都是一个新的对象,也即是说引用a和b指向的是两个不同的对象, * 因此语句System.out.println(a == b)输出:false。 */ String a = new String("123"); String b = new String("123"); System.out.println(a == b); //false
/* * 3.编译期确定(1) * a和b中的"helloworld"都是字符串常量,它们在编译期就被确定了,所以a==b为true; * c中"hello"与"world"也为字符串常量,当一个字符串由多个字符串常量连接而成时, * 它自己肯定也是字符串常量,所以c也同样在编译期就被解析为一个字符串常量, * 所以c也是常量池中"helloworld”的一个引用。所以我们得出a==b==c。 */ String a = "helloworld"; String b = "helloworld"; String c = "hello" + "world"; System.out.println(a == b);//true System.out.println(a == c);//true
/* * 4.编译期无法确定(1) * 用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。 * a还是常量池中"helloworld”的引用,b因为无法在编译期确定,所以是运行时创建的新对象"helloworld”的引用, * c因为有后半部分new String("world")所以也无法在编译期确定,所以也是一个新创建对象"helloworld”的引用。 */ String a = "helloworld"; String b = new String("helloworld"); String c = "hello" + new String("world"); System.out.println(a == b);//false System.out.println(a == c);//false System.out.println(b == c);//false
/* * 5.编译期无法确定(2) * 因为c指向堆中的"abcdef"对象,而"abcdef"是字符串池中的对象,所以结果为false */ String a = "abc"; //池中创建"abc",然后在堆中创建"abc"对象,然后将堆中这个地址返回给a String b = "def"; //池中创建"def",然后在堆中创建"def"对象,然后将堆中这个地址返回给b String c = a + b; // 堆中创建"abcdef"将地址返回给c System.out.println(c == "abcdef");//false
/* * 6.编译期优化 * 在程序编译期,JVM就将常量字符串的"+"连接优化为连接后的值,拿"a" + 1来说, * 经编译器优化后在class中就已经是a1。在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为true。 */ String a = "a1"; String b = "a" + 1; System.out.println( a == b);//true String c = "docter"; String d = "doc" + "ter"; System.out.println( c == d);//true
/* * 7.编译期无法确定(3) * JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的, * 即"a" + b无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给c。所以上面程序的结果也就为false。 */ String a = "ab"; String b = "b"; String c = "a"+ b; System.out.println(a == c);//false
/* * 8.比较字符串常量的“+”和字符串引用的“+”的区别 * 为什么出现上面的结果呢?这是因为,字符串字面量拼接操作是在Java编译器编译期间就执行了, * 也就是说编译器编译时,直接把"java"、"isbest"和"language"这三个字面量进行"+"操作得到一个"javaisbestlanguage" 常量, * 并且直接将这个常量放入字符串池中,这样做实际上是一种优化,将3个字面量合成一个,避免了创建多余的字符串对象。 * 而字符串引用的"+"运算是在Java运行期间执行的,即b + c + d在程序执行期间才会进行计算, * 它会在堆内存中重新创建一个拼接后的字符串对象。总结来说就是:字面量"+"拼接是在编译期间进行的, * 拼接后的字符串存放在字符串池中;而字符串引用的"+"拼接运算实在运行时进行的,新创建的字符串存放在堆中。 * 对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如"I"+"love"+"java"; 的字符串相加, * 在编译期间便被优化成了"Ilovejava"。对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。 * * */ String a = "javaisbestlanguage"; String b = "java"; String c = "isbest"; String d = "language"; System.out.println(a == "java" + "isbest" + "language");//true System.out.println(a == b + c + d);//false
/* * 9.编译期确定(2) * 和例子7中唯一不同的是b字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。 * 所以此时的"a" + b和"a" + "b"效果是一样的。故上面程序的结果为true。 * */ String a = "ab"; final String b = "b"; String c = "a" + b; System.out.println(a == c);//true
/* * 10.编译期无法确定(4) * 虽然用final修饰了b,但getB()方法必须在运行期间执行 */ String a = "ab"; final String b = getB(); String c = "a" + b; System.out.println((a == c));//false private static String getB(){ return "b"; }
来源:https://www.cnblogs.com/pfzhu/p/10255842.html