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 "/opt/jdk/jre/bin/java": 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 SE 13 新增特性

    Java SE 13 新增特性 作者:Grey 原文地址:Java SE 13 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new ...

  8. Java Hour 13 集合基础

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

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

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

随机推荐

  1. pytho学习笔记---编码

    编解码 ASCII:1字节,0-255 GBK2313:常用的汉字,2万多个 GBK:对GBK2313的补充,支持藏文,2个字节表示一个汉字 big5:台湾,繁体字 unicode:万国码,2-4字节 ...

  2. subString(index,end) 用法

    sb = sb.Substring(0, sb.Length - 1); 获取当前字符串的前一部分去掉最后一个字符

  3. Matplotlib--基本使用

    基础应用 import matplotlib.pyplot as plt import numpy as np #使用np.linspace定义x:范围是(-1,1);个数是50. 仿真一维数据组(x ...

  4. 为docker私有registry配置nginx反向代理

    公司的Docker私有registry已经搭建好了,用官方的registry image很容易就搭建好了.现在就是要用nginx的反向代理把它放出来,以便在外网可以访问. 我的上一篇blog 讲了如何 ...

  5. cdnbest区域自定义配置里添加防xss攻击配置

    把下面代码复制进去即可: <!--#start 300 --><config> <response action='allow' > <table name= ...

  6. 如何用poi生成导出excel

    import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Sheet; import java. ...

  7. xhprof 安装详解

    准备工作1.xhprof不支持php7,需要php7以下版本2.php扩展模块xhprof下载地址: http://pecl.php.net/get/xhprof-0.9.4.tgz xhprof安装 ...

  8. PHP错误日志记录:display_errors与log_errors的区别

    我们所做的东西,无论在开发环境还是在生产环境都可能会出现一些问题. 开发环境下,我们会要求错误尽可能详细的呈现出来,错误提示信息越详细越好,越详细越能帮助开发人员确定问题所在并从根本上解决他们. 生产 ...

  9. [leetcode]1. Two Sum两数之和

    Given an array of integers, return indices  of the two numbers such that they add up to a specific t ...

  10. linux主机名设置

    有时会报错: 代理抛出异常错误: java.net.MalformedURLException: Local host name unknown: java.net.UnknownHostExcept ...