一、实例化方式
1、直接赋值:
String str = "Hello World"; //str是一个对象,那么"Hello World"应该保存在堆内存中
System.out.println(str);
这种赋值方法最为常用,但是String本身毕竟是一个类,既然是类,那么类中一定存在构造方法。
public String (String str);
2、使用构造方法实例化:
String str = new String("Hello World");
System.out.println(str);
二、字符串比较
1、如果要比较两个int型变量是否相等,只需要用"=="验证
例子:
int x = 10;
int y = 10;
System.out.println(x == y);
结果:
true
在String中也可以用"=="比较,看以例子:
/**
* @Author WFG
* @Date 2019/5/16 17:49
*/
public class TestDemo1 {
public static void main(String[]args){
String str1 = "Hello World";
String str2 = new String("Hello World");
String str3 = str2; //引用传递
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str2 == str3);
}
}
结果:
false
false
true
通过以上例子可以发现,字符串内容相同,但用"=="判断的结果不一样,究其原因,还得从内存来看:
通过以上分析可知,"=="比较的不是字符串对象包含的内容,而是两个对象保存的内存地址数值比较,也就是"=="属于数值比较,比较的是内存地址。
如果想要比较对象的内容,则必须使用String类提供的equals方法
范例:
/**
* @Author WFG
* @Date 2019/5/16 17:49
*/
public class TestDemo1 {
public static void main(String[]args){
String str1 = "Hello World";
String str2 = new String("Hello World");
String str3 = str2; //引用传递
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
System.out.println(str2.equals(str3));
}
}
结果:
true
true
true
小结:
(1)“==”比较的是两个字符串内存地址的数值是否相等,属于数值比较。
(2)“equals()”比较的是两个字符串的内容,属于内容比较。
三、String的匿名对象
在任何语言的底层,都没有提供字符串的数据类型定义,现在所谓的字符串只是高级语言提供给用户方便开发的支持而已。很多语言都是使用字符数组来描述字符串的概念。在java中也没有提供字符串的概念,依然不属于基本数据类型。字符串作为String类的匿名对象存在的。(所有使用""定义的内容本质上来讲都是String类的匿名对象)。
观察字符串常量:
public class TestDemo2 {
public static void main(String[]args){
String str1 = "Hello";
String str2 = new String("Hello");
System.out.println(str1.equals(str2));
System.out.println("Hello".equals(str2));
}
}
结果:
true
true
匿名对象可以调用类中的方法和属性,而以上的字符串调用了equals()方法,所以它是一个对象。
PS:任何字符串常量都是Stringn的匿名对象,所以该对象用永远不会为null。如果要判断用户输入的字符串是否等同于特定字符串,一定要将特定字符串写在前面,以防出现NullPointerException问题。
四、String类两种实例化的区别
回顾上面讲到的两种实例化操作,在实际开发中,如何选择呢?
1、采用直接赋值:
String str = "Hello";
此时只分配了一块栈内存和堆内存空间:
再来看代码:
public class TestDemo2 {
public static void main(String[]args){
String str1 = "hello" ;
String str2 = "hello" ;
String str3 = "hello" ;
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str2 == str3);
}
}
结果:
true
true
true
我们发现所有直接赋值的String类的对象的内存地址完全相同,内存分配如下:
为什么没有开辟新的内存空间呢?
String类的设计使用了共享设计模式
在JVM底层实际上会自动维护一个对象池(字符串对象池),如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池中。如果下次继续使用直接赋值的模式声明String类对象,此时对象池中年如若有指定内容,将直接进行引用;如果没有,则开辟新的字符串对象然后将其保存在对象池中以供下次使用。(所谓对象池就是一个对象数组,目的是为了减少开销)
2、采用构造方法
类对象使用构造方法实例化是标准做法,使用构造方法一定要new,而一旦使用new就表示分配新的内存空间:
String str = new String("Hello") ;
内存分配如下:
通过分析可知,如果使用String构造方法就会开辟两块堆内存空间,并且其中一块堆内存将成为垃圾空间,除了这一缺点外,也会对字符串共享产生问题。
public class TestDemo2 {
public static void main(String[]args){
String str1 = new String("Hello");
String str2 = "Hello";
System.out.println(str1 == str2);
}
}
结果:
false
使用构造方法定义的String类对象,其内容不会保存在对象中(因为重新分配了一块新的内存)。
现在如果使用构造方法定义String类的对象,其内容要保存在对象中该怎么办?我们可以使用String类定义的一个手动入池的办法:
public String intern();
范例:
public class TestDemo2 {
public static void main(String[]args){
String str1 = new String("Hello").intern();
String str2 = "Hello";
System.out.println(str1 == str2);
}
}
结果:
true
小结:
String类两种实例化的区别:
(1)直接赋值实例化(string str = "xxx"):只会分配一块堆内存空间,并且对象自动入池,以供重复使用;
(2)构造方法实例化(string str = new string("xxx")):会分配两块内存空间,其中一块是垃圾,并且不会自动入池,用户可以使用intern()方法手动入池。
五、字符串不可变更
字符串一旦定义不可变更。
所有的语言对于字符串的底层实现,都是字符数组,数组的最大缺陷就是长度固定。在定义字符串常量时它的内容不可变更。
范例:
public class TestDemo1 {
public static void main(String[]args){
String str = "Hello";
str += "World";
str = str + "!!!";
System.out.println(str);
}
}
结果:
HelloWorld!!!
通过内存图分析一下:
通过以上分析可知,字符串内容的更改,其实是改变的字符串对象的引用,并且伴随着大量的垃圾在实际开发中应该避免。