char类型只能表示单个字符,不能表示由多个字符连接而成的字符串。可以通过java.lang包中的String类来创建字符串。
声明字符串变量:
String str; //相当于声明String str = null;
也可以声明多个字符串变量:
String a,b,c;
1. 字符串实例化的两种常用方法
① 引用字符串常量
String a = "hello world" ;
或者先申明再赋值
String str1,str2;
str1 = "你好";
str2 = "大海";
代码例子:得到
public class Test {
public static void main(String[] args){
String a = "hello world";
String str1,str2;
str1 = "你好";
str2 = "大海";
System.out.println(a);
System.out.println(str1 + str2);
}
}
② 利用构造方法赋值并实例化
使用new关键字实例化:
String a =new String("hello world");
代码例子:
public class Test {
public static void main(String[] args){
String a = new String("hello world");
System.out.println(a);
}
}
结果:
hello world
2. 字符串实例化结果比较
来看一个例子:以下三个字符串内容一致,但是使用==比较,发现有的比较结果却是false,为什么会这样?
public class Test {
public static void main(String[] args){
String stra = "hello";
String strb = new String("hello");
String strc = strb; //引用传递
System.out.println(stra == strb); // false
System.out.println(strb == strc); // true
System.out.println(stra == strc); // false
}
}
结果:
false
true
false
原因:实际上 == 两端比较的是两个对象(任意的引用类型)的堆内存的地址,而不是内容。
而要比较字符串内容建议使用String类的equals方法,代码例子:
public class Test {
public static void main(String[] args){
String stra = "hello";
String strb = new String("hello");
String strc = strb; //引用传递
System.out.println(stra.equals(strb)); //true
System.out.println(strb.equals(strc)); //true
System.out.println(stra.equals(strc)); //true
}
}
结果:
true
true
true
【经典问题】String 比较中 == 与equals的区别?
- "=="是关系运算符,可以进行数值比较,如果用在String上,表示对象内存地址数值比较,不能比较内容。
- equals是String类自己定义的方法,用于进行字符串内容的比较。
3. 两种字符串实例化方法的区别
主要是内存申请上有区别
引用字符串常量
当采用直接赋值方式实例化字符串对象时,此时内存会开辟一块堆内存,堆内存的字符串常量池空间将保存"hello" 字符串数据,并且栈内存将直接引用此堆内存空间。
且当有相同的字符串常量引用或者赋值时,字符串实例化不会再额外申请堆内存空间,来看一个例子:
public class Test {
public static void main(String[] args){
String stra = "hello";
String strb = "hello";
String strc = "nihao";
System.out.println(stra==strb); //true
System.out.println(strb==strc); //false
System.out.println(stra==strc); //false
}
}
结果:
true
false
false
此时内存分配图为:
这就涉及到了JVM的共享设计模式:在JVM底层实际上会有一个对象池(不一定保存String对象),当代码之中使用了直接赋值的方式定义了一个String类对象时,会将此字符串对象所使用的匿名对象入池保存,而如果后续还有其他String类对象也采用直接赋值方式,并且设置了同样的内容的时候,将不会开辟新的堆内存空间,而是使用已有的对象进行引用的分配,从而继续使用。
② 利用构造方法赋值并实例化
public class Test {
public static void main(String[] args){
String str = new String("hello");
System.out.println(str);
}
}
当我们使用构造方法实例化String对象时,因为每一个字符串都是一个String类的匿名对象,所以会首先在堆内存中申请一块内存空间保存字符串"hello word",而后又使用new关键字申请另一块堆内存空间,而真正使用的是用new关键字开辟的堆内存,而之前定义的字符串常量开辟的堆内存空间将不会被任何的栈内存所指向,成为垃圾空间,并等待被GC回收。所以,使用构造方法的方式开辟的字符串对象,实际上会开辟两块空间,其中一块空间将成为垃圾,如图所示:
由以上内存分析可以发现,通过构造方法进行实例化对象操作,最终会产生两块堆内存,其中一块是垃圾空间。另外还因为new关键字申请了新内存,从而导致其内容不会保存到对象池中,如果有必要入池则需要手动入常量池。
例子:未入池。
public class HTest {
public static void main(String[] args){
String stra = new String("hello"); //构造方法
String strb = "hello"; //直接赋值
System.out.println(stra == strb); //false
}
}
结果:
false
例子:使用intern()方法将字符串对象入池。
public class Test {
public static void main(String[] args) {
String stra = new String("hello").intern(); //构造方法
String strb = "hello"; //直接赋值
System.out.println(stra == strb); //true
}
}
结果:
true
【经典问题】:String类两种对象的实例化区别是什么?
- 直接赋值(String str ="字符串"):仅开辟一块堆内存空间,且对象可以自动入池以共其他对象重复使用;
- 构造方法(String str = new String("字符串")):开辟两块堆内存空间,且有一块会成为垃圾,产生的对象不会自动入池,需要使用intern()方法手动入池。
总结:
String 对象实例化两种方式的区别
直接赋值:只会产生一个实例化对象,并且库自动保存到对象池之中,以实现字符串实例的重用。
构造方法:会产生两个实例化对象,并且不会自动入池,无法实现对象重用,但是可以利用intern()方法手工入池处理。
池的概念:String对象(常量)池,该对象池的主要目的是实现数据的共享处理。