String类常量
字符串常量是String类的匿名类对象。
在程序的开发中,任何一个整数都是int类型,任何一个小数都是double类型,但是对于字符串而言,程序之中不会提供有字符串这样的基本数据类型,那么可以提供的只是String类,所以任何使用“"”定义的常用类实际上都是描述的一个String类的匿名对象
public class JavaDemo {
public static void main(String[] args) {
String s1 = "123";
System.out.println("123".equals(s1));
}
}
字符串常量“123”可以调用equals方法,说明“123”常量是一个String的匿名对象。
equals()方法里面提供有一个回避null的判断,所以如果将字符串常量写在前面,那么调用equals()方法的时候永远都不可能出现“NullPointerException”,字符串常量一定是一个匿名对象,一定具有堆内存地址。
String类两种实例化方式区别
String类对象有两种实例化方式,分析这两种实例化方式的区别:
1. 分析直接复制的对象实例化方式;
public class JavaDemo {
public static void main(String[] args) {
String s1 = "123";
}
}
这种情况下,只会开辟一块堆内存空间。
利用直接复制实例化String的形式还可以实现同一个字符串对象数据的共享。
public class JavaDemo {
public static void main(String[] args) {
String s1 = "123";
String s2 = "123";
System.out.println(s1 == s2); //地址比较
}
}
此时程序返回“true”,我们可以得出结论,这两个对象所指向同一块堆内存。
这时我们可以提出一个疑问:s1和s2是两个不同的匿名对象,为什么他们的堆内存地址会相同呢?
在这里我们引入一个String类池的概念。
当一个String类的匿名对象被实例化的时候,在堆地址中不会直接存一个字符串,而是存在有一个String类池(类似数组),当String类池中没有数据时,会将数据保存在String类池中,而String类池中有数据的时候,该String类的栈地址会直接指向String类池的那一部分。
先看一段代码:
public class JavaDemo {
public static void main(String[] args) {
String strA = "mldn";
String strB = "mldnjava";
String strC = "mldn";
System.out.println(strA == strC); //地址比较
}
}
true
底层运行的具体步骤是:
- strA的匿名对象被实例化,此时String类池没有“mldn”的数据,所以将“mldn”的数据存入String类池;
- strB的匿名对象被实例化,此时String类池中没有“mldnjava”的数据,所以将“mldnjava”的数据存入String类池;
- strC的匿名对象被实例化,此时String类池中已存在“mldn”的数据,所以strC 的栈内存直接指向String类池中“mldn”部分的堆内存。
图例分析:
在采用直接复制的处理过程中,对于字符串而言可以实现池数据的自动保存,当有相同数据定义时可以减少对象的产生,以提升我们的操作性能。
对于equals来说只要字符串相等其返回值都为true
2. 分析构造方法实例化
public class JavaDemo {
public static void main(String[] args) {
String str = new String("mldn");
}
}
此时会开辟两块内存空间,而后只会使用一块,而另外一块由字符串常量所定义的匿名对象将称为我们的垃圾空间。
public class JavaDemo {
public static void main(String[] args) {
String strA = "mldn";
String strB = new String("mldn");
System.out.println(strA == strB);
}
}
false
在使用构造方法实例化String对象时不会自动出现保存到字符串池的的处理。
根据上代码可知strB的开辟的内存不再String类池中。
手工入池
public class JavaDemo {
public static void main(String[] args) {
String strA = "mldn";
String strB = new String("mldn").intern();
System.out.println(strA == strB);
}
}
true
在使用构造方法定义对象之后由于使用了intern()方法,所以即使时构造出来的String类对象的内容也可以实现对象池的管理。
面试题:请解释String类对象实例化方式的区别?
- 直接赋值:只会产生一个实例化对象,并且可以自动保存到对象池之中,以实现该字符串实例的重用。
- 构造方法:会产生两个实例化对象,并且不会自动入池,无法实现对象重用,但是可以利用intern()方法手工入池处理。
String对象(常量)池
对象池的主要目的时实现数据的共享处理。以String对象池为例,里面的内容主要就是为了重用,而重用实际上就属于共享设计,但是在Java之中对象(常量)池实际上可以分为两种:
- 静态常量池:指的是程序(*.class)在加载的时候会自动将此程序之中保存的字符串、普通的常量、类和方法的信息等等,全部进行分配;
- 运行时常量池:当一个程序(*.class)加载之后,里面可能有一些变量,这个时候提供的常量池。
范例:观察一个程序(静态常量池)
public class JavaDemo {
public static void main(String[] args) {
String strA = "www.mldn.cn";
String strB = "www." + "mldn." + "cn";
System.out.println(strA == strB);
}
}
true
本程序中所给出的内容全部都是常量数据(字符串的常量都是匿名对象),所以最终在程序加载的时候会自动帮助开发者处理好相应的连接。
案例:观察另外一种情况
public class JavaDemo {
public static void main(String[] args) {
String info="mldn";
String strA = "www.mldn.cn";
String strB = "www." + info + "cn";
System.out.println(strA == strB);
}
}
false
这时候之所以是一个false,是因为程序在加载的时候并不确定info是什么内容,因为在进行字符串的连接的时候info采用的是一个变量,变量的内容是可以修改的,所以它不认为最终的strB的结果就是一个所需的最终的结果。
所以在比较两个字符串的内容时,使用equals()方法进行修改;