1、String是不可变类。
这句话想必大家已烂熟于心了,究其原因呢?一般以为:对象一旦创建完成是不会因外界的改变而改变
首先看一个简单的例子:
1 String str= "abc"; 2 System.out.println("str: " + str); // 输出s:abc 3 str = "def"; 4 System.out.println("str: " + str); // 输出s:def
此时看上去,str输出结果变了,那么是否与上面的说法自相矛盾呢?答案是否定的,因为str只是指向堆内存中的引用,存储的是对象在堆中的地址,而非对象本身,str本身存储在栈内存中。
实际上,此时堆内存中依然存在着"abc"和"def"对象。对于"abc"对象本身而言,对象的状态是没有发生任何变化的。
那么为什么String类具有不可变性呢,显然,既然不可变说明String类中肯定没有提供对外可setters方法。接下来来具体看一下String类的定义。
下面是String类中主要属性的定义(Java 1.7源码):
private final char value[]; private int hash; // Default to 0 }
与之前版本的Java String源码相比,String类减少了int offset 和 int count的定义。这样变化的结果主要体现在:
1.避免之前版本的String对象subString时可能引起的内存泄露问题;
2.新版本的subString时间复杂度将有O(1)变为O(n);
通过上面String类的定义,类名前面用了final class修饰,因此,String类不能被继承。对于其属性定义,可以看出,属性value[]和hash都是被定义成private类型,且由于没有提供对外的public setters方法,String类属性不可被改变。
其中,需要重点关注属性value[],其被final char修饰,因此字符型数组value只会被赋值一次就不可修改。其存储内容正好是String中的单个字符内容。
1.2创建String类的对象的两种方式:
- ""直接赋值法
- new关键字法
此处的==比较是内存地址,equal方法默认比较的也是内存地址,而此处string类重写了equal方法,将字符串转化为字符数组,比较的是字符数组中的字符内容是否一致。
Q:new string (“ABC”);创建了几个对象?
A:2个,一个是字符串常量池。另一个是位于堆内存中
注:使用string的equal方法时,尽量使用常量作为方法的调用者,这样不会报错。
class MyString {
public static void main (String [] args) {
test(null);
}
public static void test(String str){
if("中国".equal(str)){
System.out.println("回答正确");
}else{
System.out.println("回答错误");
}
}
如果写成str.equal("中国"),如果传入的参数为null时,会报空指针异常。
2、string的构造方法
String(); 构造一个空字符串对象
String(byte[] bytes); 通过byte数组构造字符串对象
String(byte[] bytes,int offset,int length);通过byte数组,从offset开始,总共length长的字节构造字符串对象
String(char[] value); 通过char数组构造字符串对象
String(byte[] char,int offset,int length);通过char数组,从offset开始,总共length长的字节构造字符串对象
String(String original); 构造一个original的副本,拷贝一个original
String(StringBuffer buffer);通过StringBuffer数组构造字符串对象
public class Demo01 {
public static void main(String[] args) {
// 字节数组
byte[] bArray = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' };
// 字符数组 char[] cArray = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }; //声明一个StringBuffer StringBuffer strbuf = new StringBuffer("strbuf"); // 实例一个String对象 String str = new String("str abcd"); //实例一个String对象 通过一个btye数组构造字符串对象(字节数组) String strb = new String(bArray); //实例一个String对象 通过一个char数组构造字符串对象(字符数组) String strc = new String(cArray); //实例一个String对象 通过一个char数组构造字符串对象(字节数组,开始的数据,截得数据长度) String strbIndex = new String(bArray, 1, 5); //实例一个String对象 通过一个char数组构造字符串对象(字符数组,开始的数据,截得数据长度) String strcIndex = new String(cArray, 1, 2); //实例一个String对象 通过一个StringBuffer对象构造字符串对象
StringBuilder strbuff =new StringBuilder();
strbuff.append("jack");
String strbuff = new String(strbuf); System.out.println("实例一个带参String对象: "+str); System.out.println("实例一个带byte数组参数String对象: "+strb); System.out.println("实例一个带char数组参数String对象: "+strc); System.out.println("实例一个带byte数组参数String对象,截取从1开始截取,截5位: "+strbIndex); System.out.println("实例一个带char数组参数String对象,截取从1开始截取,截2位: "+strcIndex); System.out.println("实例一个带StringBuffer参数String对象: "+strbuff); // 如果是字节类型,将输出地址 System.out.println(bArray); // 如果是字符类型,将输出字符 System.out.println(cArray); } }
输出的结果:
实例一个带参String对象: str abcd
实例一个带byte数组参数String对象: abcdefgh
实例一个带char数组参数String对象: abcdefgh
实例一个带byte数组参数String对象,截取从1开始截取,截5位: bcdef
实例一个带char数组参数String对象,截取从1开始截取,截2位: bc
实例一个带StringBuffer参数String对象: jack
[B@499a12ee
abcdefgh