#Thinking In Java# Chapter13 String
不可变String
String类中看起来会修改String的方法,实际上均为创建了一个全新的对象,而最初的对象丝毫未动,对方法传递字符串,实际传递的是引用的一个拷贝,而该引用所指的对象一直待在原物理位置上,从未动过。
重载“+”与String builder
package strings;
public class WhitherStringBuilder {
public String implicit(String[] fields) {
String result = "";
for (int i = 0; i < fields.length; i++) {
result += fields[i];
}
return result;
}
public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < fields.length; i++) {
result.append(fields[i]);
}
return result.toString();
}
}
方法一使用了多个String对象,方法二使用了StringBuilder,运行javap -c WitherStringBuilder查看字节码。可知对于implicit方法,在每一次循环内部都生成了一个新的StringBuilder对象。而explicit方法循环部分代码更简短,只生成一个StringBuilder对象,显示地创建还可以预先为StringBuilder指定大小,如果已经知道最后的字符串大小大概有多长,可以预先指定大小避免多次重新分配缓存。
因此如果为一个类编写toString()方法时,如果字符串操作比较简单,可以信赖编译器。但如果药在toString方法中使用循环,最好自己创建一个StringBuilder对象,用它来构造最终的结果。
参考示例
package strings;
import java.util.Random;
public class UsingStringBuilder {
public static Random rand = new Random(47);
public String toString() {
StringBuilder result = new StringBuilder("[");
for (int i = 0; i < 25; i++) {
result.append(rand.nextInt(100));
result.append(",");
}
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]
*/
StringBuilder提供了丰富全面的方法,包括insert(),replace(),substring()甚至reverse(),但最常用的还是append()和toString(),还有delete()方法,StringBuilder是Java SE5引入的,在这之前用的是StringBuffer。后者是线程安全,开销大,所以在Java SE 5/6,字符串操作应该更快。
无意识的递归之toString()方法
如果你希望 toString()方法打印出对象的内存地址,也许你会考虑使用this关键字
package strings;
import java.util.ArrayList;
import java.util.List;
public class InfiniteRecursion {
@Override
public String toString() {
return "InfiniteRecursion address: " + this + "
";
//此处使用this造成的递归,你看出来了吗?( ̄▽ ̄)"
}
public static void main(String[] args) {
List<InfiniteRecursion> v = new ArrayList<InfiniteRecursion>();
for (int i = 0; i < 10; i++) {
v.add(new InfiniteRecursion());
}
System.out.println(v);
}
}
然而在打印时却会一串非常长的异常,这里发生了自动类型转换,由InfiniteRecursion类型转换成String类型。因为编译器看到一个String对象后接着一个“+”,而后面的对象并不是String,再次发生类型转换,调用this的toString()方法,发生递归调用。
如果真的想要打印出地址,应该调用Object.toString(),所以应该调用super.toString()方法,而不是thos
String上的操作
以下是String对象具备的一些基本方法。重载的方法归纳在同一行中:
构造器 | 参数,重载版本 | 应用 |
---|---|---|
length() | String中字符个数 | |
charAt() | Int索引 | 取得String该索引位置上的char |
getChars(),getBytes() | 要复制部分起点和终点的索引,要复制的目标数组,目标数组的起始索引 | 复制char或byte到一个目标数组里 |
toCharArray() | 生成一个char[],包含String的所有字符 | |
equals(), equalsIgnoreCase() | 与之进行比较的String | 比较两个String的内容是否相同 |
compareTo() | 与之进行比较的String | 按词典顺序比较String的内容,比较结果为正数,负数,零 |
contains() | 要搜索的CharSequence | 如果String对象包含参数的内容,则返回true |
contentEquals() | 与之比较的CharSequence或StringBuffer | 如果该String与参数的内容完全一致 |
equalsIgnoreCase() | 与之进行比较的String | 忽略大小写,如果两个String内容相同,返回true |
regionMatcher() | 该索引的偏移量,另一个String及其索引偏移量,要比较的长度。重载版本增加了“忽略大小写功能” | 返回boolean结果,以表明所比较区域是否相等 |
startsWith | 可能的起始String。重载版本在参数中增加了偏移量 | 返回boolean结果,以表明该String是否以此参数开始 |
endsWith | 该String可能的后缀String | 返回boolean结果,以表明此参数是否是该字符串的后缀 |
indexOf(),lastIndexOf() | 重载版本包括:char, char与起始索引,String, String与起始索引 | 如果该String不包含该参数,就返回-1;否则返回该参数在String中。LastIndexOf()是从后往前搜索 |
substring()(subSequence()) | 重载版本:起始索引;起始索引+终点坐标 | 返回一个新的String以包含指定的子字符串 |
concat() | 要连接的String | 返回一个新的String对象,内容为原始String连接上参数String |
replace() | 要替换掉的字符,用来进行替换的新字符。也可以用一个CharSequence来替换另一个CharSequence | 返回替换字符后的新String对象,如果没有替换发生,则返回原来的String对象 |
toLowerCase() toUpperCase() | 将字符的大小写改变后,返回一个新String对象。如果没有发生改变,则返回原始的String对象 | |
trim() | 将String两端的空白字符删除后,返回一个新的String对象。如果没有发生改变,则返回原始的String对象 | |
valueOf() | Object; char[];char[], 偏移量,与字符个数;boolean; char; int; long; float; double | 将基本数据型态转换成 String |
intern() | 为每个唯一的字符序列生成一个且仅生成一个String引用 |
格式化输出
System.out.format()
package strings;
public class SimpleFormat {
public static void main(String[] args) {
int x = 5;
double y = 3.1415926;
System.out.println("Row 1 : [" + x + "" + y + "]");
//new ways
System.out.format("Row 1: [%d %f]
", x, y);
System.out.printf("Row 1: [%d %f]
", x, y);
}
}
/*
Row 1 : 53.1415926]
Row 1: [5 3.141593]
Row 1: [5 3.141593]
*/
Formatter类
在Java中,所有新的格式化功能java.util.Formatter。可以将Formatter看作一个翻译器,将格式化字符串与数据翻译成需要的结果,当你创建一个Formatter对象的时候,需要向构造器传递一些信息,告诉他最终结果向哪里输出:
package strings;
import javax.swing.plaf.synth.SynthEditorPaneUI;
import java.io.PrintStream;
import java.util.Formatter;
public class Turtle {
private String name;
private Formatter f;
public Turtle(String name, Formatter f) {
this.name = name;
this.f = f;
}
public void move(int x, int y) {
f.format("%s The turtle is at (%d , %d)
", name, x, y);
}
public static void main(String[] args) {
PrintStream outAlias = System.out;
Turtle tom = new Turtle("Tom", new Formatter(System.out));
Turtle jerry = new Turtle("Jerry", new Formatter(outAlias));
tom.move(0, 0);
jerry.move(4, 8);
tom.move(3, 4);
jerry.move(2, 5);
tom.move(3, 3);;
jerry.move(3, 3);
}
}
/*
tommy The turtle is at (0 , 0)
Turtle The turtle is at (4 , 8)
tommy The turtle is at (3 , 4)
Turtle The turtle is at (2 , 5)
tommy The turtle is at (3 , 3)
Turtle The turtle is at (3 , 3)
*/
所有的tom都会被输入得到System.out中,而所有的jerry都会输出到System.out的别名上。Formatter的构造器重载后可以接收多种输出目的地,包括PrintStream(),OutputStream和File
格式化说明符
抽象语法:%[argument_index$] [flags] [width] [.precision] conversion
默认情况下,数据是右对齐,可以使用“-”左对齐
width:指明最大尺寸,可以应用于各种类型的数据转换,并且行为方式都一样
precision: 不是所有类型的数据都能使用precision,而且应用于不同的类型数据含义不同,在将precision应用于String时,表示String可输出字符的最大数量(注意体会与width的区别,width强调的是尺寸),而precision应用于浮点数表示小数部分要显示出来的位数。小数位数过多舍入,太少尾部补0。precision不能应用于整数,因为整数没有小数部分。
package strings;
import java.util.Formatter;
public class Receipt {
private double total = 0;
private Formatter f = new Formatter(System.out);
public void printTitle() {
f.format("%-15s %5s %10s
", "Item", "Qty", "Price");
f.format("%-15s %5s %10s
","----", "---", "-----");
}
public void print(String name, int qty, double price) {
f.format("%-15.15s %5d %10.2f
",name, qty, price);
//%-15.15s,-表示左对齐,前15表示width,后15表示precision
total += price;
}
public void printTotal() {
f.format("%-15s %5s %10.2f
", "Tax", "", total * 0.06);
f.format("%-15s %5s %10s
", "", "", "-----");
f.format("%-15s %5s %10.2f
", "Total", "", total * 1.06);
}
public static void main(String[] args) {
Receipt receipt = new Receipt();
receipt.printTitle();
receipt.print("Jack's Magic Beans", 4, 4.25);
receipt.print("Princess Peas", 3, 5.1);
receipt.print("Three Bears Porridge", 1, 14.29);
receipt.printTotal();
}
}
/*
Item Qty Price
---- --- -----
Jack's Magic Be 4 4.25
Princess Peas 3 5.10
Three Bears Por 1 14.29
Tax 1.42
-----
Total 25.06
*/
Formatter转换
类型转换字符
字符 | 含义 |
---|---|
d | 整数型 |
c | Unicode |
b | boolean |
s | String |
f | 浮点值 |
e | 浮点数(科学计数) |
x | 整数(十六进制) |
h | 散列码(十六进制) |
% | 字符“%” |
荔枝:
Formatter f = new Formatter();
int i = 0;
f.format("b : %b", i);
/*
b : true
*/
b转换:对于boolean基本类型或者Boolean对象,其转换类型永远是true或false,对其他类型的参数,只要该参数不为null,那么转换的结果永远为true,即使是数字0,这与C等其他语言中不一样,需要小心。
便捷的String.format()
String.format()是一个static方法,它接受与Formatter.format()方法一样的参数,但是,但是,但是,它返回一个String对象。当你只需format()方法一次的时候,String.format()使用起来非常方便。其实在String.format()内部它也创建了一个Formatter对象,然后将参数传入Formatter。但不如使用便捷的String.format()方法并且使代码更具有可读性。
package strings;
public class DatabaseException extends Exception {
public DatabaseException(int transactionID, int queryId, String message) {
super(String.format("(transactionID%d, queryId%d) %s", transactionID, queryId, message));
}
public static void main(String[] args) {
try {
throw new DatabaseException(3, 7, "write failed");
} catch (Exception e) {
System.out.println(e);
}
}
}
dump工具(十六进制转换)
package strings;
import java.io.*;
public class Hexadecimal {
public static String format(byte[] data) {
StringBuilder result = new StringBuilder();
int n = 0;
for (byte b : data) {
if (n % 16 == 0)
result.append(String.format("%05X: ", n)); // 占用5个位置(16进制表示)
result.append(String.format("%02X ", b)); // 占用2个位置(16进制表示)
n++;
if (n % 16 == 0)
result.append("
");
}
result.append("
");
return result.toString();
}
public static void main(String[] args) throws Exception {
if (args.length == 0)
System.out.println(format(BinaryFile.read("E:/ThinkingInJava/src/strings/Hexadecimal.java")));
else
System.out.println(format(BinaryFile.read(new File(args[0]))));
}
}
/*
00010: 0D 0A 0D 0A 69 6D 70 6F 72 74 20 6A 61 76 61 2E
00020: 69 6F 2E 2A 3B 0D 0A 0D 0A 70 75 62 6C 69 63 20
...
*/
package strings;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class BinaryFile {
public static byte[] read(File bFile) throws IOException {
BufferedInputStream bf = new BufferedInputStream(new FileInputStream(
bFile));
try {
byte[] data = new byte[bf.available()];
bf.read(data);
return data;
} finally {
bf.close();
}
}
public static byte[] read(String bFile) throws IOException {
return read(new File(bFile).getAbsoluteFile());
}
}