1.不可变字符串
String对象是不可变的。String类中每一个看起来会修改String值的方法,实际上是创建了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未变。
看看下面的代码:
public class Immutable {
public static String upcase(String s) {
return s.toUpperCase();
}
public static void main(String[] args) {
String q = "howdy";
System.out.println(q); // howdy
String qq = upcase(q);
System.out.println(qq); // HOWDY
System.out.println(q); // howdy q还是q
}
}
当q传给upcase()方法时,实际传递的是引用的一个拷贝。其实,每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过。
2.重载“+”与StringBuilder
String对象是不可变的,你可以给一个String对象加任意多的别名。因为String对象具有只读特性,所以指向它的任何引用都不可能改变它的值,因此,也就不回对其他的引用有什么影响。
不可变性会带来一定的效率问题。为String对象重载的“+”操作符就是一个例子。重载的意思是,一个操作符应用于特定的类时,被赋予了特殊的意义(用于String的“+”与“+=”是java中仅有的两个重载过的操作符,而java并不允许程序员重载任何操作符)。操作符“+”可以用来连接String:
public class Test {
public static void main(String[] args) {
String mango ="mango";
String s = "abc" + mango + "def" + 47;
System.out.println(s);
}
}
String有一个append()方法,它会生成一个新的String对象,以包含“abc”与mango连接好的字符串。然后该对象再与“def”相连,生成另一个新的String对象。
在使用”+”操作符时,编译器自动引入了java.lang.StringBuilder类,避免了连加情况下产生大量需要回收的垃圾(每+一次就会产生一个新的字符串)。因此,当为一个类编写toString()方法时,如果字符串操作简单,可以信赖编译器,它会为你合理的构造最终字符串结果。但是如果要在toString()中使用循环,那么最好自己创建一个StringBuilder对象,结合append()用它来构造最终结果。
例子如下:
public class UsingStringBuilder {
public static Random random = new Random(47);
public String toString(){
StringBuilder result = new StringBuilder("[");
for (int i = 0; i < 25; i++) {
result.append(random.nextInt(100));
result.append(",");
}
// 书上的result.length()-2是得不到它那个结果的,得到的结果是[58,55,93,61,61,29,68,0,22,7,88,28,51,89,9,78,98,61,20,58,16,40,11,22,]
result.delete(result.length()-1, result.length());
result.append("]");
return result.toString();
}
public static void main(String[] args) {
UsingStringBuilder usb = new UsingStringBuilder();
System.out.println(usb);
}
}
输出:[58,55,93,61,61,29,68,0,22,7,88,28,51,89,9,78,98,61,20,58,16,40,11,22,4]
最终的结果是用append()语句一点点拼接起来的。如果你想走捷径,例如append(a+”:”+c),那编译器就会掉入陷阱,从而为你另外创建一个StringBuilder对象处理括号内的字符串操作。
StringBuilder提供了丰富而全面的方法,包括insert()、replace()、subString()甚至reverse(),但最常用的还是append()和toString(),delete()方法。StringBuilder是Java SE5引入的,在这之前还有Java的StringBuffer。StringBuffer是线程安全的,因此开销会大一些。StringBuilder字符串操作会更快一些。
3.无意识的递归
java中的每个类从基本上都是继承自Object,标准容器类自然也不例外。因此容器类都有toString()方法,并且覆写了该方法,使得它生成的String结果能够表达容器自身,以及容器所包含的对象。例如ArrayList.toString(),它会遍历ArrayList中包含的所有对象,调用每个元素上的
toString()方法。
如果希望toString()方法打印出对象的内存地址,不可以使用this关键字。如果这样写:
public class InfiniteRecursion {
public String toString() {
// return " InfiniteRecursion address: " + this + "\n";
return " InfiniteRecursion address: " + super.toString() + "\n";
}
public static void main(String[] args) {
List<InfiniteRecursion> v = new ArrayList<InfiniteRecursion>();
for (int i = 0; i < 3; i++) {
v.add(new InfiniteRecursion());
System.out.println(v);
}
}
}
输出为:
[ InfiniteRecursion address: com.test.InfiniteRecursion@6bbc4459
]
[ InfiniteRecursion address: com.test.InfiniteRecursion@6bbc4459
, InfiniteRecursion address: com.test.InfiniteRecursion@152b6651
]
[ InfiniteRecursion address: com.test.InfiniteRecursion@6bbc4459
, InfiniteRecursion address: com.test.InfiniteRecursion@152b6651
, InfiniteRecursion address: com.test.InfiniteRecursion@544a5ab2
]
编译器会试图将”+”后面的this转换为String,此时调用toString()函数,导致无穷递归。因此如果想要打印出内存地址,应该调用Object.toString()方法,所以调用super.toString()就可以了。
4.String上的操作
以下是String对象具备的一些基本方法。
从图中可以看出,当需要改变字符串的内容时,String类的方法都会返回一个新的String对象。同时,如果内容没有发生改变,String的方法只是返回指向原对象的引用而已。这可以节约存储空间以及避免额外的开销。