Thinking in Java Chapter 13

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()能够接受一个整数作为参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。

时间: 2024-12-28 17:53:49

Thinking in Java Chapter 13的相关文章

Head first java chapter 13 运用swing

老白的JAVA课程13 接口

之前我们讲到了java4大特征的最后一个抽象的特征,其实在抽象这个特征里面,我们还有一个特征-接口,今天我们就来学一学接口.  用电脑,手机等电子产品来比喻接口,可以更好的理解,比如用符合接口的插头就可以了.  接口具有很好的扩展性,方便.  接口的由来   例子:奥拓奥迪都是汽车,汽车的维护都是可以用调节引擎和更换汽油的方法,那么奥拓和奥迪也都可以:但是有了更多的维护对象以后,比如说大货车,卡车等等,其他的对象不一定需要每一步维护,或者都不需要维护:又比如不同枪支有不同特性的例子.  因此,我

《linux 内核完全剖析》chapter 13 内存管理 (不含swap.c)

内存管理(memory.c 和swap.s 部分) "倒着看" 先看memory management,很明显,前面各种阻力,都是因为涉及内存管理.不先看这个,我估计前面看了也是白看 我估算着理论打基础砸了差不多一个星期的时间在memory management上面了...感觉很有收获,是时候用实践(code)印证理论了! <modern operating system>讲内存管理那一章 http://blog.csdn.net/cinmyheart/article/de

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

原文:零元学Expression Blend 4 - Chapter 13 用实例了解布局容器系列-「Pathlistbox」I 本系列将教大家以实做案例认识Blend 4 的布局容器,此章介绍的布局容器是Blend 4 里的-「Pathlistbox」 ? 本系列将教大家以实做案例认识Blend 4 的布局容器,此章介绍的布局容器是Blend 4 里的-「Pathlistbox」 ? 就是要让不会的新手都看的懂! ? <先来了解Pathlistbox的基本功能> 01 开启一个新专案後,在主

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

Java基础13:反射与注解详解 什么是反射? 反射(Reflection)是Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性. Oracle官方对反射的解释是 Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fi

java 21 - 13 IO流之序列化和反序列化

序列化流:把对象按照流一样的方式存入文本文件或者在网络中传输.对象 -- 流数据(ObjectOutputStream) 构造方法:ObjectInputStream(InputStream in)  创建从指定 InputStream 读取的 ObjectInputStream 反序列化流:把文本文件中的流对象数据或者网络中的流对象数据还原成对象.流数据 -- 对象(ObjectInputStream) 构造方法:ObjectInputStream(InputStream in)  创建从指定

java 21 - 13 IO流之 合并流

SequenceInputStream :表示其他输入流的逻辑串联. 构造方法摘要 SequenceInputStream(Enumeration<? extends InputStream> e)           通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数. SequenceInputStream(InputStream s1, InputStream s2) 通过记住

java 19 -13 FIle类的一些方法2

1 package zl_file; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.text.SimpleDateFormat; 6 import java.util.Date; 7 8 /* 9 判断功能: 10 public boolean isDirectory():判断是否是目录(文件夹) 11 public boolean isFile():判断是否是文件 12 public boolean e

java 16 - 13 可变参数和Arrays工具类的asList()方法

可变参数:定义方法的时候不知道该定义多少个参数 格式: 修饰符 返回值类型 方法名(数据类型… 变量名){ } 注意: 这里的变量其实是一个数组 如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个 1 import java.util.Scanner; 2 public class ArgsDemo { 3 4 public static void main(String[] args) { 5 int result = sum(1,2,3,4,5,6,7,8,9);//参与计