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()能够接受一个整数作为参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。
Thinking in Java Chapter 13的更多相关文章
- Java系列,《Java核心技术 卷1》,chapter 13,集合
13.1.2 Java类库中的集合接口和迭代器接口 删除元素,对于next和remove的调用是互相依赖的,如果调用remove之前没有调用next,则会跑出IllegalStateExcep ...
- Chapter 13. Miscellaneous PerlTk Methods PerlTk 方法杂项:
Chapter 13. Miscellaneous PerlTk Methods PerlTk 方法杂项: 到目前为止,这本书的大部分章节 集中在特定的几个部件, 这个章节覆盖了方法和子程序 可以被任 ...
- Java基础13:反射与注解详解
Java基础13:反射与注解详解 什么是反射? 反射(Reflection)是Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性. Orac ...
- 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 ...
- 零元学Expression Blend 4 - Chapter 13 用实例了解布局容器系列-「Pathlistbox」I
原文:零元学Expression Blend 4 - Chapter 13 用实例了解布局容器系列-「Pathlistbox」I 本系列将教大家以实做案例认识Blend 4 的布局容器,此章介绍的布局 ...
- 033 01 Android 零基础入门 01 Java基础语法 03 Java运算符 13 运算符和表达式知识点总结
033 01 Android 零基础入门 01 Java基础语法 03 Java运算符 13 运算符和表达式知识点总结 本文知识点:运算符和表达式知识点总结 前面学习的几篇文都是运算符和表达式相关的知 ...
- Java SE 13 新增特性
Java SE 13 新增特性 作者:Grey 原文地址:Java SE 13 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new ...
- Java Hour 13 集合基础
有句名言,叫做10000小时成为某一个领域的专家.姑且不辩论这句话是否正确,让我们到达10000小时的时候再回头来看吧. 本文作者Java 现经验约为13 Hour,请各位不吝赐教. Java 中的集 ...
- JAVA必备——13个核心规范
标准的价值: 你听过这句话吗?"一流企业做标准.二流企业做品牌.三流企业做产品!"我时我就在想,做标准的企业就是一流的?卖产品就是三流公司?而坐产品或者加工的公司,即使说销售量非常 ...
随机推荐
- postgresql数据库varchar、char、text的比较
名字 描述character varying(n), varchar(n) 变长,有长度限制character(n), char(n) 定长,不足补空白text 变长,无长度限制简单来说,varcha ...
- 大数据spark学习第一周Scala语言基础
Scala简单介绍 Scala(Scala Language的简称)语言是一种能够执行于JVM和.Net平台之上的通用编程语言.既可用于大规模应用程序开发,也可用于脚本编程,它由由Martin Ode ...
- Java 集合类实现原理
转载自:http://blog.csdn.net/qq_25868207/article/details/55259978 :##ArrayList实现原理要点概括 参考文献:http://zhang ...
- linux查询公网ip
查询公网ip[出口IP] 1.curl icanhazip.com 2.curl ipecho.net/plain 3.curl www.trackip.net/i
- python中的元组
在python中,元组是不可变类型 可通过以下案例说明: >>> c1 = ['1','2'] >>> c = (1,2,c1) >>> c (1 ...
- Yii2.0关闭自带的debug功能
1.找到相应模块的config文件夹的main-local.php文件注释相关代码,如下: 2.将web下面的两个入口文件改成false index.php index-test.php
- ASP.NET CORE 2.0 发布到IIS,IIS如何设置环境变量来区分生产环境和测试环境
0.前言 因为给前端的测试环境是windows,所以要设置windows上的环境变量,如果上Linux就没有这篇文章了,所以大家不要在意为什么core不放在linux上. 1.网上的解决方案 a 方式 ...
- EasyWeChat使用(laravel框架下)
最近做了个项目是关于微信网页开发的,今天记录下在做项目中的关于微信这块遇到的一些坑 关于微信这块,用的是EasyWeChat,提高了开发的效率.在看EasyWeChat这个文档的时候发现了有专门针对l ...
- MySQL终章
视图 什么是视图 是一个虚拟表,其内容由查询定义.同真实的表一样,视图包含一系列带有名称的列和行数据 视图的特点 . 视图的列可以来自不同的表,是表的抽象和逻辑意义上建立的新关系. . 视图是由基本表 ...
- RNA-seq数据综合分析教程 AKAP95
https://blog.csdn.net/l_yivs?t=1 RNA-seq数据综合分析教程 2 4,055 A+ 所属分类:Transcriptomics 收 藏 2 RNA-se ...