// strings/WhitherStringBuilder.java
public class WhitherStringBuilder {
public String implicit(String[] fields) {
String result = "";
for(String field : fields) {
result += field;
}
return result;
}
public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for(String field : fields) {
result.append(field);
}
return result.toString();
}
}
在长久的等待之后,Java SE5 终于推出了 C 语言中 printf() 风格的格式化输出这一功能。这不仅使得控制输出的代码更加简单,同时也给与Java开发者对于输出格式与排列更强大的控制能力。
printf()
C 语言的 printf() 并不像 Java 那样连接字符串,它使用一个简单的格式化字符串,加上要插入其中的值,然后将其格式化输出。 printf() 并不使用重载的 + 操作符(C语言没有重载)来连接引号内的字符串或字符串变量,而是使用特殊的占位符来表示数据将来的位置。而且它还将插入格式化字符串的参数,以逗号分隔,排成一行。例如:
System.out.printf("Row 1: [%d %f]%n", x, y);
这一行代码在运行的时候,首先将 x 的值插入到 %d_ 的位置,然后将 y 的值插入到 %f 的位置。这些占位符叫做格式修饰符,它们不仅指明了插入数据的位置,同时还指明了将会插入什么类型的变量,以及如何格式化。在这个例子中 %d 表示 x 是一个整数,%f 表示 y 是一个浮点数(float 或者 double)。
// strings/Turtle.java
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)
*/
格式化修饰符 %s 表明这里需要 String 参数。
所有的 tommy 都将输出到 System.out,而所有的 terry 则都输出到 System.out 的一个别名中。Formatter 的重载构造器支持输出到多个路径,不过最常用的还是 PrintStream()(如上例)、OutputStream 和 File。你可以在 附录:流式 I/O 中了解更多信息。
// strings/Splitting.java 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-words
}
}
/* Output:
[Then,, when, you, have, found, the, shrubbery,, you,
must, cut, down, the, mightiest, tree, in, the,
forest...with..., a, herring!]
[Then, when, you, have, found, the, shrubbery, you,
must, cut, down, the, mightiest, tree, in, the, forest,
with, a, herring]
[The, whe, you have found the shrubbery, you must cut
dow, the mightiest tree i, the forest...with... a
herring!]
*/
第二个和第三个 split() 都用到了 \\W,它的意思是一个非单词字符(如果 W 小写,\\w,则表示一个单词字符)。通过第二个例子可以看到,它将标点字符删除了。第三个 split() 表示“字母 n 后面跟着一个或多个非单词字符。”可以看到,在原始字符串中,与正则表达式匹配的部分,在最终结果中都不存在了。
String.split() 还有一个重载的版本,它允许你限制字符串分割的次数。
用正则表达式进行替换操作时,你可以只替换第一处匹配,也可以替换所有的匹配:
// strings/Replacing.java
public class Replacing {
static String s = Splitting.knights;
public static void main(String[] args) {
System.out.println(
s.replaceFirst("f\\w+", "located"));
System.out.println(
s.replaceAll("shrubbery|tree|herring","banana"));
}
}
/* Output:
Then, when you have located the shrubbery, you must cut
down the mightiest tree in the forest...with... a
herring!
Then, when you have found the banana, you must cut down
the mightiest banana in the forest...with... a banana!
*/
第一个表达式要匹配的是,以字母 f 开头,后面跟一个或多个字母(注意这里的 w 是小写的)。并且只替换掉第一个匹配的部分,所以 “found” 被替换成 “located”。
// strings/Finding.java
import java.util.regex.*;
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())
System.out.print(m.group() + " ");
System.out.println();
int i = 0;
while(m.find(i)) {
System.out.print(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
*/
public int groupCount() 返回该匹配器的模式中的分组数目,组 0 不包括在内。
public String group() 返回前一次匹配操作(例如 find())的第 0 组(整个匹配)。
public String group(int i) 返回前一次匹配操作期间指定的组号,如果匹配成功,但是指定的组没有匹配输入字符串的任何部分,则将返回 null。
public int start(int group) 返回在前一次匹配操作中寻找到的组的起始索引。
public int end(int group) 返回在前一次匹配操作中寻找到的组的最后一个字符索引加一的值。
下面是正则表达式组的例子:
// strings/Groups.java
import java.util.regex.*;
public class Groups {
public static final String POEM =
"Twas brillig, and the slithy toves\n" +
"Did gyre and gimble in the wabe.\n" +
"All mimsy were the borogoves,\n" +
"And the mome raths outgrabe.\n\n" +
"Beware the Jabberwock, my son,\n" +
"The jaws that bite, the claws that catch.\n" +
"Beware the Jubjub bird, and shun\n" +
"The frumious Bandersnatch.";
public static void main(String[] args) {
Matcher m = Pattern.compile(
"(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$")
.matcher(POEM);
while(m.find()) {
for(int j = 0; j <= m.groupCount(); j++)
System.out.print("[" + m.group(j) + "]");
System.out.println();
}
}
}
/* Output:
[the slithy toves][the][slithy toves][slithy][toves]
[in the wabe.][in][the wabe.][the][wabe.]
[were the borogoves,][were][the
borogoves,][the][borogoves,]
[mome raths outgrabe.][mome][raths
outgrabe.][raths][outgrabe.]
[Jabberwock, my son,][Jabberwock,][my son,][my][son,]
[claws that catch.][claws][that catch.][that][catch.]
[bird, and shun][bird,][and shun][and][shun]
[The frumious Bandersnatch.][The][frumious
Bandersnatch.][frumious][Bandersnatch.]
*/
这首诗来自于 Lewis Carroll 所写的 Through the Looking Glass 中的 “Jabberwocky”。可以看到这个正则表达式模式有许多圆括号分组,由任意数目的非空白符(\\S+)及随后的任意数目的空白符(\\s+)所组成。目的是捕获每行的最后3个词,每行最后以 \$ 结束。不过,在正常情况下是将 \$ 与整个输入序列的末端相匹配。所以我们一定要显式地告知正则表达式注意输入序列中的换行符。这可以由序列开头的模式标记 (?m) 来完成(模式标记马上就会介绍)。
// strings/ReFlags.java
import java.util.regex.*;
public class ReFlags {
public static void main(String[] args) {
Pattern p = Pattern.compile("^java",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher(
"java has regex\nJava has regex\n" +
"JAVA has pretty good regular expressions\n" +
"Regular expressions are in Java");
while(m.find())
System.out.println(m.group());
}
}
/* Output:
java
Java
JAVA
*/
// strings/TheReplacements.java
import java.util.regex.*;
import java.nio.file.*;
import java.util.stream.*;
/*! Here's a block of text to use as input to
the regular expression matcher. Note that we
first extract the block of text by looking for
the special delimiters, then process the
extracted block. !*/
public class TheReplacements {
public static void main(String[] args) throws Exception {
String s = Files.lines(
Paths.get("TheReplacements.java"))
.collect(Collectors.joining("\n"));
// Match specially commented block of text above:
Matcher mInput = Pattern.compile(
"/\\*!(.*)!\\*/", Pattern.DOTALL).matcher(s);
if(mInput.find())
s = mInput.group(1); // Captured by parentheses
// Replace two or more spaces with a single space:
s = s.replaceAll(" {2,}", " ");
// Replace 1+ spaces at the beginning of each
// line with no spaces. Must enable MULTILINE mode:
s = s.replaceAll("(?m)^ +", "");
System.out.println(s);
s = s.replaceFirst("[aeiou]", "(VOWEL1)");
StringBuffer sbuf = new StringBuffer();
Pattern p = Pattern.compile("[aeiou]");
Matcher m = p.matcher(s);
// Process the find information as you
// perform the replacements:
while(m.find())
m.appendReplacement(sbuf, m.group().toUpperCase());
// Put in the remainder of the text:
m.appendTail(sbuf);
System.out.println(sbuf);
}
}
/* Output:
Here's a block of text to use as input to
the regular expression matcher. Note that we
first extract the block of text by looking for
the special delimiters, then process the
extracted block.
H(VOWEL1)rE's A blOck Of tExt tO UsE As InpUt tO
thE rEgUlAr ExprEssIOn mAtchEr. NOtE thAt wE
fIrst ExtrAct thE blOck Of tExt by lOOkIng fOr
thE spEcIAl dElImItErs, thEn prOcEss thE
ExtrActEd blOck.
*/
// strings/SimpleRead.java
import java.io.*;
public class SimpleRead {
public static BufferedReader input =
new BufferedReader(new StringReader(
"Sir Robin of Camelot\n22 1.61803"));
public static void main(String[] args) {
try {
System.out.println("What is your name?");
String name = input.readLine();
System.out.println(name);
System.out.println("How old are you? " +
"What is your favorite double?");
System.out.println("(input: <age> <double>)");
String numbers = input.readLine();
System.out.println(numbers);
String[] numArray = numbers.split(" ");
int age = Integer.parseInt(numArray[0]);
double favorite = Double.parseDouble(numArray[1]);
System.out.format("Hi %s.%n", name);
System.out.format("In 5 years you will be %d.%n", age + 5);
System.out.format("My favorite double is %f.", favorite / 2);
} catch(IOException e) {
System.err.println("I/O exception");
}
}
}
/* Output:
What is your name?
Sir Robin of Camelot
How old are you? What is your favorite double?
(input: <age> <double>)
22 1.61803
Hi Sir Robin of Camelot.
In 5 years you will be 27.
My favorite double is 0.809015.
*/
// strings/BetterRead.java
import java.util.*;
public class BetterRead {
public static void main(String[] args) {
Scanner stdin = new Scanner(SimpleRead.input);
System.out.println("What is your name?");
String name = stdin.nextLine();
System.out.println(name);
System.out.println(
"How old are you? What is your favorite double?");
System.out.println("(input: <age> <double>)");
int age = stdin.nextInt();
double favorite = stdin.nextDouble();
System.out.println(age);
System.out.println(favorite);
System.out.format("Hi %s.%n", name);
System.out.format("In 5 years you will be %d.%n",
age + 5);
System.out.format("My favorite double is %f.",
favorite / 2);
}
}
/* Output:
What is your name?
Sir Robin of Camelot
How old are you? What is your favorite double?
(input: <age> <double>)
22
1.61803
Hi Sir Robin of Camelot.
In 5 years you will be 27.
My favorite double is 0.809015.
*/
// strings/ThreatAnalyzer.java
import java.util.regex.*;
import java.util.*;
public class ThreatAnalyzer {
static String threatData =
"58.27.82.161@08/10/2015\n" +
"204.45.234.40@08/11/2015\n" +
"58.27.82.161@08/11/2015\n" +
"58.27.82.161@08/12/2015\n" +
"58.27.82.161@08/12/2015\n" +
"[Next log section with different data format]";
public static void main(String[] args) {
Scanner scanner = new Scanner(threatData);
String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@" +
"(\\d{2}/\\d{2}/\\d{4})";
while(scanner.hasNext(pattern)) {
scanner.next(pattern);
MatchResult match = scanner.match();
String ip = match.group(1);
String date = match.group(2);
System.out.format(
"Threat on %s from %s%n", date,ip);
}
}
}
/* Output:
Threat on 08/10/2015 from 58.27.82.161
Threat on 08/11/2015 from 204.45.234.40
Threat on 08/11/2015 from 58.27.82.161
Threat on 08/12/2015 from 58.27.82.161
Threat on 08/12/2015 from 58.27.82.161
*/
// strings/ReplacingStringTokenizer.java
import java.util.*;
public class ReplacingStringTokenizer {
public static void main(String[] args) {
String input =
"But I'm not dead yet! I feel happy!";
StringTokenizer stoke = new StringTokenizer(input);
while(stoke.hasMoreElements())
System.out.print(stoke.nextToken() + " ");
System.out.println();
System.out.println(Arrays.toString(input.split(" ")));
Scanner scanner = new Scanner(input);
while(scanner.hasNext())
System.out.print(scanner.next() + " ");
}
}
/* Output:
But I'm not dead yet! I feel happy!
[But, I'm, not, dead, yet!, I, feel, happy!]
But I'm not dead yet! I feel happy!
*/