From Thinking in Java 4th Edition

String对象是不可变的。String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动:

import static net.mindview.util.Print.*;

public class Immutable {
public static String upcase(String s) {
return s.toUpperCase();
} public static void main(String[] args){
String q = "howdy";
print(q); // howdy String qq = upcase(q);
print(qq); // HOWDY
print(q); // howdy
}
} /* Output:
howdy
HOWDY
howdy
*/

当把q传给upcase()方法时,实际传递的是引用的一个拷贝。每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直在单一的物理位置上,从未动过。

不可变性会带来一定的效率问题。为String对象重载的“+”操作符就是一个好的例子:

public class Concatenation {
public static void main(String[] args) {
String mango = "mango";
String s = "abc" + "mango" + "def" + 47;
System.out.println(s);
}
} /* Output:
abcmangodef47
*/

可以想象这段代码这样工作:String对象很可能有一个append()方法,它会生成一个新的String对象,以包含“abc”与mango连接后的字符串。然后,该对象再与“def”相连,生成另一个新的String对象,以此类推。

[软件设计中的一个教训:除非你用代码将系统实现,并让它动起来,否则你无法真正了解它会有什么问题。]

可以在代码中使用StringBuilder来生成一个String:

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();
}
}

1. 在implicit()方法中,编译器创建的StringBuilder实在循环体内够早的,这意味着每经过一次循环,就会创建一个新的StringBuilder对象。

2. 在explicit()方法中,它只生成了一个StringBuilder对象。

因此,当你为一个类编写toString()方法时,如果字符串操作比较简单,那就可以信赖编译器为你创建合理的字符串结果。但是,如果你要在toString()方法中使用循环,那么最好自己创建一个StringBuilder对象用来构造最后的结果:

import java.util.*;

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() - 2, result.length());
result.append("]"); return result.toString();
} public static void main(String[] args){
UsingStringBuilder usb = new UsingStringBuilder();
System.out.println(usb);
}
} /* Output:
[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()方法。

无意识的递归

Java中的每个类从根本上都是继承自Object,标准容器也不例外,因此容器都有toString()方法:

import generics.coffee.*;
import java.util.*; public class ArrayListDisplay {
public static void main(String[] args){
ArrayList<Coffee> coffees = new ArrayList<Coffee>(); for(Coffee c : new vCoffeeGenerator(10))
coffees.add(c); System.out.println(coffees);
}
}

如果你希望toString()方法打印出对象的内存地址,也许你会考虑使用this关键字:

import java.util.*;

public class InfiniteRecursion {
public String toString() {
return " InfiniteRecursion address: " + this + "\n";
} 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 address " + this

时,发生了自动类型转化。由InfiniteRecursion类型转换成String类型。因为编译器看到一个String对象后面跟着一个“+”,而再后面的对象不是String,于是编译器试着将this转换成衣蛾String。它的转化真是通过调用this上的toString()方法,于是就发生了递归调用。

如果你真的想打印对象的内存地址,你应该调用Object.toString()方法,这才是负责此任务的方法。所以你不应该使用this,而是应该调用super.toString()方法。

String上的操作

当需要改变字符串的内容时,String类的方法都会返回一个新的String对象。同时,如果内容没有发生改变,String的方法只是返回指向原对象的引用而已。

格式化输出[System.out.format()]

Java SE5引入的format()方法可用于PrintStream或PrintWriter对象,其中也包括System.out对象。format()方法模仿自C的printf()。如果你比较怀旧,也可以使用printf():

public class SimpleFormat {
public static void main(String[] args){
int x = 5;
double y = 5.332542; // The old way:
System.out.println("Row 1: [" + x + " " + y + "] "); // The new way:
System.out.format("Row 1: [%d %f]\n", x, y);
// or
System.out.printf("Row 1: [%d %f]\n", x, y);
}
} /* Output:
Row 1: [5, 5.332542]
Row 1: [5, 5.332542]
Row 1: [5, 5.332542]
*/

在Java中,所有新的格式化功能都由java.util.Formatter类处理。可以将Formatter看作一个翻译器,它将你的格式化字符串与数据翻译成所需要的结果:

import java.io.*;
import java.util.*; 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)\n", name, x, y);
} public static void main(String[] args){
PrintStream outAlias = System.out; Turtle tommy = new Turtle("Tommy", new Formatter(System.out));
Turtle terry = new Turtle("Terry", new Formatter(outAlias)); tommy.move(0, 0);
terry.move(4, 8); tommy.move(3, 4);
terry.move(2, 5); tommy.move(3, 3);
terry.move(3, 3);
}
} /* Output:
Tommy The Turtle is at (0, 0)
Terry The Turtle is at (4, 8)
Tommy The Turtle is at (3, 4)
Terry The Turtle is at (2, 5)
Tommy The Turtle is at (3, 3)
Terry The Turtle is at (3, 3)
*/

格式化说明符

以下是其抽象的用法:

%[argument_index$][flags][width][.precision]conversion

1. width: 用于指定一个域的最小尺寸。Formatter对象通过在必要时补零来确保一个域至少达到某个长度。默认右对齐,“-”可以改变对齐方向。

2. precision: 对于String对象,用于指定字符最大数量;对浮点数,表示小数部分要显示出来的位数(默认是6位)。不能应用于整数,会触发异常

下面用格式修饰符来打印一个购物收据:

import java.util.*;

public class Receipt {
private double total = 0;
private Formatter f = new Formatter(System.out); public void printTitle() {
f.format("%-15s %5s %10s\n", "Item", "Qty", "Price");
f.format("%-15s %5s %10s\n", "----", "---", "-----");
} public void print(String name, int qty, double price){
f.format("%-15.15s %5d %10.2f\n", name, qty, price);
total += price;
} public void printTotal() {
f.format("%-15s %5s %10.2f\n", "Tax", "", total * 0.06);
f.format("%-15s %5s %10s \n", "", "", "-----");
f.format("%-15s %5s %10.2f\n", "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();
}
} /* Output:
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
*/

格式转换符b的转换结果为true或false。但只要其参数不是null,那转换结果就都是true,即使是数字0,其结果也是true。而在其他语言如C中,数字0的转换为false,这点需要注意。

String.format()

Java SE5也参考了C中的sprintf()方法, 以生成格式化的String对象。String.format()是一个static方法,它接受与Formatter.format()方法一样的参数,但返回一个String对象。当你只使用format()方法一次的时候,String.format()用起来很方便:

public class DatabaseException extends Exception {
public DatabaseException(int transactionID, int queryID, String message){
super(Stirng.format("(t%d, q%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);
}
}
} /* Output:
DatabaseException: (t2, q7) Write failed
*/

一个十六进制转存的工具

下面的小工具使用了String.format()方法,以可读的十六进制格式将字节数组打印出来:

package net.mindview.util;
import java.io.*; public class Hex {
public static String format(byte[] data) {
StringBuilder result = new StringBuilder();
int n = 0; for(byte b : data) {
if(0 == n % 16)
result.append(String.format("%05X: ", n)); result.append(String.format("%02X ", b));
++n;
if(0 == n % 16) result.append("\n");
} result.append("\n");
return result.toString();
} public static void main(String[] args){
if(0 == args.length)
// Test by display this class file:
System.out.println(format(BinaryFile.read("Hex.class")));
else
System.out.println(format(BinaryFile.read(new File(args[0]))));
}
}

正则表达式

一般说来,正则表达式以某种方式来描述字符串,因此你可以说“如果一个字符串含有这些东西,那么它就是我正在找的东西。

Java对反斜杠\有着特殊的处理。在正则表达式中\d表示一位数字。在其它语言中,\\表示“我想要在正则表达式中插入一个普通的(literal)反斜杠,请不要给它任何特殊的意义”。而在Java中,\\的意思是“我要插入一个正则表达式的反斜杠,所以其后的字符具有特殊的意义。”例如,如果你在Java中想要表示一位数,那么其正则表达式应该是\\d。如果要插入一个普通的反斜杠,则应该是\\\。不过换行符和制表符之类的东西只需要使用单反斜杠:\n\t。

1. 表示可能有,应该使用"?"

2. 表示一个或多个之前的表达式,应该使用"+"

所以要表示“可能有一个负号,后面跟着一位数或多位数”,可以这样:

-?\\d+

应用正则表达式的最简单的途径,就是利用String类内建的功能。例如,你可以检查一个String是否匹配如上所述的正则表达式:

public class IntegerMatch {
public static void main(String[] args){
System.out.println("-1234".matches("-?\\d+"));
System.out.println("5678".matches("-?\\d+"));
System.out.println("+911".matches("-?\\d+"));
System.out.println("+911".matches("(-|\\+)?\\d+"));
}
} /* Output:
true
true
false
true
*/

String类中还自带了一个非常有用的正则表达式工具——split()方法,其功能是“将字符串从正则表达式匹配的地方切开”:

import java.util.*;

public class Splitting {
public static String knights =
"Then, when you have found the shrubbery, you must" +
"cut down the mightiest tree in the forest ..." +
"with ... a herring!"; public static void split(String regex){
System.out.println(Arrays.toString(knights.split(regex)));
} public static void main(String[] args){
split(" "); // Doesn't have to contain regex chars
split("\\W+"); // Non-word characters
split("n\\W+"); // 'n' followed by non-word characters
}
} /* Output:
[Then,, when, you, have, found, the, shrubbery,, you, mustcut, down, the, mightiest, tree, in, the, forest, ...with, ..., a, herring!]
[Then, when, you, have, found, the, shrubbery, you, mustcut, down, the, mightiest, tree, in, the, forest, with, a, herring]
[The, whe, you have found the shrubbery, you mustcut dow, the mightiest tree i, the forest ...with ... a herring!]
*/

\W表示非单词字符(如果W小写,\w,则表示一个单词字符)。可以看到,在原始字符串中,与正则表达式匹配的部分,在最终的结果中都不存在了。

String.split()还有一个重载版本,可以限制字符串分割的次数。

String类自带的最后一个正则表达式工具是“替换”。你可以只替换正则表达式第一个匹配的子串,或是替换所有匹配的地方:

import static net.mindview.util.Print.*;

public class Replacing {
static String s = Splitting.knights; public static void main(String[] args){
print(s.replaceFirst("f\\w+", "located"));
print(s.replaceAll("shrubbery|tree|herring", "banana"));
}
}

下面的每个正则表达式都能成功匹配字符序列“Rudolph”:

public class Rudolph {
public static void main(String[] args){
for(String pattern : new String[]{"Rudolph", "[rR]udolph", "[rR][aeiou][a-z]ol.*", "R.*"})
System.out.println("Rudolph".matches(pattern));
}
} /* Output:
true
true
true
true
*/

Pattern和Matcher

比起功能有限的String类,我们更愿意构造功能强大的正则表达式对象。只需导入java.util.regex包,然后用static Pattern.compile()方法来编译你的正则表达式。[在Unix/Linux上,命令行中的正则表达式必须用引号括起来。]

import java.util.regex.*;
import static net.mindview.util.Print.*; public class TestRegularExpression {
public static void main(String[] args){
if(args.length < 2) {
print("Usage: \njava TestRegularExpression " + "characterSequence regularExpression+"); System.exit(0);
} print("Input: \"" + args[0] + "\""); for(String arg : args){
print("Regular expression: \"" + arg + "\"");
Pattern p = Pattern.compile(arg);
Matcher m = p.matcher(args[0]); while(m.find()){
print("Match \"" + m.group() + "\" at positions " + m.start() + "-" + (m.end() - 1));
}
}
}
}

通过调用Pattern.matcher()方法,并传入一个字符串参数,我们得到了一个Matcher对象。使用Matcher对象上的方法, 我们将能够判断各种不同类型的匹配是否成功:

boolean matches()
boolean lookingAt()
boolean find()
boolean find(int start)

其中matches()方法用来判断整个输入字符串是否匹配正则表达式模式,而lookingAt()则用来判断该字符串的起始部分是否能够匹配模式。

Matcher.find()方法可用来在CharSequence中查找多个匹配:

import java.util.regex.*;
import static net.mindview.util.Print.*; public class Finding {
public static void main(String[] args){
Matcher m = Pattern.compile("\\w+").matcher("Evening is full of the linnet's wings"); while(m.find())
printnb(m.group() + " ");
print(); int i = 0;
while(m.find(i)){
printnb(m.group() + " ");
++i;
}
}
} /* Output:
Evening is full of the linnet s wings
Evening vening ening ning ing ng g is is s full full ull ll
l of of f the the he e linnet linnet innet nnet net et t s
s wings wings ings ngs gs s
*/

find()像迭代器那样前向遍历输入字符串。而第二个find()能够接受一个整数作为参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。

Thinking in Java Chapter 13的更多相关文章

  1. Java系列,《Java核心技术 卷1》,chapter 13,集合

    13.1.2 Java类库中的集合接口和迭代器接口     删除元素,对于next和remove的调用是互相依赖的,如果调用remove之前没有调用next,则会跑出IllegalStateExcep ...

  2. Chapter 13. Miscellaneous PerlTk Methods PerlTk 方法杂项:

    Chapter 13. Miscellaneous PerlTk Methods PerlTk 方法杂项: 到目前为止,这本书的大部分章节 集中在特定的几个部件, 这个章节覆盖了方法和子程序 可以被任 ...

  3. Java基础13:反射与注解详解

    Java基础13:反射与注解详解 什么是反射? 反射(Reflection)是Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性. Orac ...

  4. linux下java.io.IOException: Cannot run program &quot;/opt/jdk/jre/bin/java&quot;: error=13, Permission denied

    linux下启动jetty时报: [root@mv01 jetty-distribution-9.2.14.v20151106]# java -jar start.jar java.io.IOExce ...

  5. 零元学Expression Blend 4 - Chapter 13 用实例了解布局容器系列-「Pathlistbox」I

    原文:零元学Expression Blend 4 - Chapter 13 用实例了解布局容器系列-「Pathlistbox」I 本系列将教大家以实做案例认识Blend 4 的布局容器,此章介绍的布局 ...

  6. 033 01 Android 零基础入门 01 Java基础语法 03 Java运算符 13 运算符和表达式知识点总结

    033 01 Android 零基础入门 01 Java基础语法 03 Java运算符 13 运算符和表达式知识点总结 本文知识点:运算符和表达式知识点总结 前面学习的几篇文都是运算符和表达式相关的知 ...

  7. Java Hour 13 集合基础

    有句名言,叫做10000小时成为某一个领域的专家.姑且不辩论这句话是否正确,让我们到达10000小时的时候再回头来看吧. 本文作者Java 现经验约为13 Hour,请各位不吝赐教. Java 中的集 ...

  8. JAVA必备——13个核心规范

    标准的价值: 你听过这句话吗?"一流企业做标准.二流企业做品牌.三流企业做产品!"我时我就在想,做标准的企业就是一流的?卖产品就是三流公司?而坐产品或者加工的公司,即使说销售量非常 ...

  9. JAVA进阶13

    间歇性混吃等死,持续性踌躇满志系列-------------第13天 1.查看线程的运行状态 package code0327; class Demo01 implements Runnable { ...

随机推荐

  1. SharePoint 2013 托管导航及相关配置

    设计完善的导航可告诉您网站的用户大量有关网站所提供业务.产品和服务的信息.通过更新导航背后的分类法,可以推动业务并保持更新,而不必在过程中重新创建其网站导航.在 SharePoint 2013 中,可 ...

  2. [MySql] - 数据库备份还原

    导出数据库到SQL方法: mysqldump.exe -u[USERNAME] -p[PASSWORD] -h [IP] jira --lock-all-tables > c:\db.sql m ...

  3. buildroot 制作Linux文件系统初级使用教程

    buildroot 下载地址:https://buildroot.org/download.html 放在Linux文件下解压出来. 使用make menuconfig 进行配置相关的东西. 在使用这 ...

  4. 使用python递归子目录处理日志文件

    重要说明: (1)python使用4个空格进行层次缩进的(不是tab),在eclipse里面可以直接使用tab缩进,是因为eclipse会实时地将tab转成4个空格 (2)在eclipse中安装pyD ...

  5. fzu 2257 saya的小熊饼干

    https://vjudge.net/problem/FZU-2257 题意:略 思路: 看题解补的题.正难则反的思想求概率. 首先,由于各维数之间是独立的.所以以x为例.首先,计算可以取到(i,j) ...

  6. java36

    1.Main public static void main(String [ ] args) public:被jvm调用的方法,权限要足够大 static:被jvm调用的方法,不需要创建对象,直接用 ...

  7. Cookie登录保存

    Login.aspx <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Log ...

  8. Golang两种方法实现MD5加密

    package main import ( "crypto/md5" "fmt" "io" ) func main() { str := & ...

  9. ACM大牛的BLOG(转)

    Twilightgod CUSThttp://blog.csdn.net/twilightgodAekdycoin FZU http://hi.baidu.com/aekdycoinForeverli ...

  10. 【原】Spring和Dubbo基于XML配置整合过程

    背景 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 单一应用架构 当网站流量很小时,只需一个 ...