String类
实例化String对象
如下代码中,各种初始化方式的效果是一样的,初始化后,String 对象的内容为 "hello" 。
// 直接赋值
String str1 = "hello";
// 构造函数方式,参数为 String
String str2 = new String("hello");
// 构造函数方式,参数为 StringBuilder
String str3 = new String( new StringBuilder("hello"));
// 构造函数方式,参数为 StringBuffer
String str4 = new String( new StringBuffer("hello"));
// 构造函数方式,参数为 char 数组
char[] cz = {'h', 'e', 'l', 'l', 'o'};
String str5 = new String(cz);
// 构造函数方式,参数为 byte 数组
byte[] bz = {'h', 'e', 'l', 'l', 'o'};
String str6 = new String(bz);
// 先使用默认构造函数,再赋值
String str7 = new String();
str7 = "hello";
}
以上方式可归纳为两类:
(1) 赋值方式
(2) 构造函数方式
传入的参数可以是String、StringBuilder、StringBuffer、char 数组、byte 数组等等。
两种实例化方式比较
一个字符串就是一个 String 类的匿名对象。
匿名对象对于这样的代码:
实际上就是在堆中开辟一个内存空间,这个空间的中存储的值为 "Jack"。然后这个空间被 name 变量所引用。
注:在JAVA中,如果一个字符串已经被一个名称所引用,则以后再有相同的字符串声明时,不会重新开辟空间,而是复用之前的空间。这样减少了不必要的空间开销。
例
String str2 = "hello";
String str3 = "hello";
以上三个String类变量本身是存放在栈内存中,但是它们指向同一块堆内存空间。
而如果使用构造函数方式初始化String类对象,和所有普通类一样,只要new一次,就会新开辟一块堆空间。
综上所述,可以看出赋值方式要优于构造函数方式。
String的内容比较
1、使用 "=="
String str1 = "hello";
String str2 = new String("hello");
String str3 = str2;
System.out.println("str1 == str2 --> " + (str1 == str2));
System.out.println("str1 == str3 --> " + (str1 == str3));
System.out.println("str2 == str3 --> " + (str2 == str3));
}
运行结果
str1 == str3 --> false
str2 == str3 --> true
从以上代码可以看出,虽然三个字符串内容完全一致,但是使用 "==" 去比较却发现并不完全相等。
这是因为每个String对象的内容实际上是保存在堆内存中的。所以,即使堆中的内容一致,并不代表它们的地址空间也一致。
2、使用equals方法
如果要比较两个字符串的内容是否相等,可以使用 equals 方法。
String str1 = "hello";
String str2 = new String("hello");
String str3 = str2;
System.out.println("str1 == str2 --> " + (str1.equals(str2)));
System.out.println("str1 == str3 --> " + (str1.equals(str3)));
System.out.println("str2 == str3 --> " + (str2.equals(str3)));
}
运行结果
str1 == str3 --> true
str2 == str3 --> true
String对象不可变
public class StringDemo03 {
public static String upcase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
String a = "hello";
System.out.println(a); // hello
String b = upcase(a);
System.out.println(b); // HELLO
System.out.println(a); // hello
}
} /* Output:
hello
HELLO
hello
*/// :~
当把a传给 upcase() 方法时,实际传递的是引用的一个拷贝。其实,每当把 String 对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过。
所以指向 String 的任何引用都不可能改变它的值。
不可变性会带来一定的效率问题。例如String类的重载操作符 "+"。
注:JAVA不同于C++,并不允许程序员自定义任何重载操作符。用于String的 "+" 和 "+=" 是JAVA中仅有的两个重载操作符。
操作符"+"可以用来拼接String。方式如下:
System.out.println(s);
/* Output:
How are you?
*/// :~
这种方式的问题在于,会产生一大堆需要垃圾回收的中间对象。
那么,如何避免这种问题呢?
JAVA中提供了两个类:StringBuilder 和 StringBuffer ,它们都有 append() 方法,效率高于 String 的 "+"。
这两个类的差别在于 StringBuffer 是线程安全的,因此开销也更大一些。
String类的常用方法
以下代码是String类的一些常用方法。
public static void main(String[] args) {
// 获取字符串字符个数
System.out.println(" Goodbye ".length());
// 获取 String 中该索引位置上的 char
System.out.println("Computer".charAt(4));
// 复制 byte 到一个目标数组
byte bytes[] = "Winter".getBytes(); // 将字符串转为 byte 数组
System.out.println( new String(bytes)); // 将完整 byte 数组转为字符串
System.out.println( new String(bytes, 1, 3)); // 将部分 byte 数组转为字符串
// 复制 char 到一个目标数组
char chars[] = new char[10];
"Summer".getChars(0, 6, chars, 2); // 将字符串0~6位置的内容拷贝到 char 数组中,从数组位置2开始
System.out.println( new String(chars)); // 将完整 char 数组转为字符串
System.out.println( new String(chars, 1, 3)); // 将部分 char 数组转为字符串
// 字符串转char数组
char[] data1 = "Baby".toCharArray();
for ( char c : data1) {
System.out.print(c + " ");
}
System.out.println();
// 如果String不包含此参数,返回-1,否则返回此参数在String中的起始索引。lastIndexOf是从后向前查找
System.out.println("How are you".indexOf("o")); // 查找返回位置
System.out.println("How are you".indexOf("o", 5)); // 查找返回位置, 从位置5开始
System.out.println("How are you".indexOf("z")); // 没有查到返回-1
System.out.println("How are you".lastIndexOf("o")); // 查找返回位置
System.out.println("How are you".lastIndexOf("o", 5)); // 查找返回位置, 从位置5开始
System.out.println("How are you".lastIndexOf("z")); // 没有查到返回-1
// 根据参数截取字符串
System.out.println("Hello World".substring(6)); // 从位置6开始截取
System.out.println("Hello World".substring(0, 5)); // 截取0~5个位置的内容
// 按照指定字符拆分字符串
String[] s = "sample@sina.com".split("@");
for ( int i = 0; i < s.length; i++) {
System.out.println(s[i]);
}
// 去除左右空格
System.out.println(" Night ".trim()); // 去除左右空格输出
// 转换大小写
System.out.println("China".toLowerCase());
System.out.println("China".toUpperCase());
// 判断是否以指定的字符串开头或结尾
if ("**NAME".startsWith("**")) {
System.out.println("**NAME 以**开头");
}
if ("NAME**".endsWith("**")) {
System.out.println("NAME** 以**结尾");
}
// 替换源子字符串为目标子字符串
System.out.println("good".replaceAll("o", "x"));
}
}
StringBuilder类
String类是不可改变的,所以你一旦创建了String对象,那它的值就无法改变了。 如果需要对字符串做很多修改,那么应该选择使用StringBuffer & StringBuilder 类。
StringBuilder 类的用法大部分与 String 类相似。
示例代码如下:
str.append("How ").append("are ").append("you?");
System.out.println(str);
/* Output:
How are you?
*/// :~
StringBuffer类
StringBuffer 类和 StringBuilder 类大致相同。
但是 StringBuffer 是线程安全的,因此开销也更大一些。
参考资料
JAVA 编程思想
JAVA 开发实战经典