7.7.3 完成程序国际化

为了让输出的字符串常量可以改变,可以将需要输出的各种字符串(不同的国家/语言环境对应不同的字符串)定义在资源包中。

Java9支持使用UTF8字符集来保存属性文件,这样在属性文件中就可以直接包含非西欧字符,因此属性文件也不再需要使用native2ascii工具进行处理。唯一要注意的是,属性文件必须显式保存为UTF-8字符集。

通常推荐源代码都使用UTF-8字符集保存。但如果使用UTF8字符集保存Java源代码,在命令行编译源程序时需要为javac显式指定-encoding utf-8选项,用于告诉Javac命令使用UTF-8字符集读取Java源文件。

如果希望程序完成国际化,只需要将不同的国家/语言(Locale)的提示信息分别以不同的文件存放即可。例如,

  • 简体中文的语言资源文件就是xxx_zh_CN.properties文件,
  • 而美国英语的语言资源文件就是xxx_en_US.properties文件

Java程序国际化的关键类是ResourceBundle,它有一个静态方法:getBundler(String baseName, Locale locale),该方法将根据Locale加载资源文件
Locale封装了一个国家、语言,例如,简体中文环境可以用简体中文的Locale代表,美国英语环境可以用美国英语的Locale代表。

从上面资源文件的命名中可以看出,不同国家、语言环境的资源文件的baseName是相同的,
baseNamemess的资源文件有很多个,不同的国家、语言环境对应不同的资源文件。
对于简体中文的Locale,则加载mess_zh_CN.properties文件。
一旦加载了该文件后,该资源文件的内容就是多个key-value对,程序就根据key来获取指定的信息,例如获取keyhello的消息,该消息是”你好!”
如果对于美国英语的Locale,则加载mess_en_US.properties文件,该文件中keyhello的消息是”Welcome!“。

java程序国际化的关键类是ResourceBundleLocale, ResourceBundle根据不同的Locale加载语言资源文件,再根据指定的key取得已加载语言资源文件中的字符串。

7.7.2 Java支持的国家和语言

如果需要获取Java所支持的国家和语言,则可调用Locale类的getAvailableLocales()方法,该方法返回一个Locale数组,该数组里包含了Java所支持的国家和语言。

下面的程序简单地示范了如何获取Java所支持的国家和语言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.*;

public class LocaleList {
public static void main(String[] args) {
// 返回Java所支持的全部国家和语言的数组
Locale[] localeList = Locale.getAvailableLocales();
// 遍历数组的每个元素,依次获取所支持的国家和语言
for (int i = 0; i < localeList.length; i++) {
// 输出出所支持的国家和语言
System.out.println(localeList[i].getDisplayCountry() + "=" + localeList[i].getCountry() + " "
+ localeList[i].getDisplayLanguage() + "=" + localeList[i].getLanguage());
}
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
阿拉伯联合酋长国=AE 阿拉伯文=ar
约旦=JO 阿拉伯文=ar
叙利亚=SY 阿拉伯文=ar
克罗地亚=HR 克罗地亚文=hr
比利时=BE 法文=fr
巴拿马=PA 西班牙文=es
马耳他=MT 马耳他文=mt
委内瑞拉=VE 西班牙文=es
= 保加利亚文=bg
台湾地区=TW 中文=zh
= 意大利文=it
= 朝鲜文=ko
= 乌克兰文=uk
= 拉托维亚文(列托)=lv
丹麦=DK 丹麦文=da
波多黎哥=PR 西班牙文=es
越南=VN 越南文=vi
美国=US 英文=en
黑山=ME 塞尔维亚文=sr
瑞典=SE 瑞典文=sv
玻利维亚=BO 西班牙文=es
新加坡=SG 英文=en
巴林=BH 阿拉伯文=ar
= 葡萄牙文=pt
沙特阿拉伯=SA 阿拉伯文=ar
= 斯洛伐克文=sk
也门=YE 阿拉伯文=ar
印度=IN 印地文=hi
= 爱尔兰文=ga
马耳他=MT 英文=en
芬兰=FI 芬兰文=fi
= 爱沙尼亚文=et
= 瑞典文=sv
= 捷克文=cs
波斯尼亚和黑山共和国=BA 塞尔维亚文=sr
= 希腊文=el
乌克兰=UA 乌克兰文=uk
= 匈牙利文=hu
瑞士=CH 法文=fr
= 印度尼西亚文=in
阿根廷=AR 西班牙文=es
埃及=EG 阿拉伯文=ar
日本=JP 日文=ja
萨尔瓦多=SV 西班牙文=es
巴西=BR 葡萄牙文=pt
= 白俄罗斯文=be
冰岛=IS 冰岛文=is
捷克共和国=CZ 捷克文=cs
= 西班牙文=es
波兰=PL 波兰文=pl
= 土耳其文=tr
西班牙=ES 加泰罗尼亚文=ca
塞尔维亚及黑山=CS 塞尔维亚文=sr
马来西亚=MY 马来文=ms
= 克罗地亚文=hr
= 立陶宛文=lt
西班牙=ES 西班牙文=es
哥伦比亚=CO 西班牙文=es
保加利亚=BG 保加利亚文=bg
= 阿尔巴尼亚文=sq
= 法文=fr
= 日文=ja
波斯尼亚和黑山共和国=BA 塞尔维亚文=sr
= 冰岛文=is
巴拉圭=PY 西班牙文=es
= 德文=de
厄瓜多尔=EC 西班牙文=es
美国=US 西班牙文=es
苏丹=SD 阿拉伯文=ar
= 英文=en
罗马尼亚=RO 罗马尼亚文=ro
菲律宾=PH 英文=en
= 加泰罗尼亚文=ca
突尼斯=TN 阿拉伯文=ar
黑山=ME 塞尔维亚文=sr
危地马拉=GT 西班牙文=es
= 斯洛文尼亚文=sl
韩国=KR 朝鲜文=ko
塞浦路斯=CY 希腊文=el
墨西哥=MX 西班牙文=es
俄罗斯=RU 俄文=ru
洪都拉斯=HN 西班牙文=es
香港=HK 中文=zh
挪威=NO 挪威文=no
匈牙利=HU 匈牙利文=hu
泰国=TH 泰文=th
伊拉克=IQ 阿拉伯文=ar
智利=CL 西班牙文=es
= 芬兰文=fi
摩洛哥=MA 阿拉伯文=ar
爱尔兰=IE 爱尔兰文=ga
= 马其顿文=mk
土耳其=TR 土耳其文=tr
爱沙尼亚=EE 爱沙尼亚文=et
卡塔尔=QA 阿拉伯文=ar
= 塞尔维亚文=sr
葡萄牙=PT 葡萄牙文=pt
卢森堡=LU 法文=fr
阿曼=OM 阿拉伯文=ar
= 泰文=th
阿尔巴尼亚=AL 阿尔巴尼亚文=sq
多米尼加共和国=DO 西班牙文=es
古巴=CU 西班牙文=es
= 阿拉伯文=ar
= 俄文=ru
新西兰=NZ 英文=en
塞尔维亚=RS 塞尔维亚文=sr
瑞士=CH 德文=de
乌拉圭=UY 西班牙文=es
= 马来文=ms
希腊=GR 希腊文=el
以色列=IL 希伯来文=iw
南非=ZA 英文=en
泰国=TH 泰文=th
= 印地文=hi
法国=FR 法文=fr
奥地利=AT 德文=de
= 荷兰文=nl
挪威=NO 挪威文=no
澳大利亚=AU 英文=en
= 越南文=vi
荷兰=NL 荷兰文=nl
加拿大=CA 法文=fr
拉脱维亚=LV 拉托维亚文(列托)=lv
卢森堡=LU 德文=de
哥斯达黎加=CR 西班牙文=es
科威特=KW 阿拉伯文=ar
= 塞尔维亚文=sr
利比亚=LY 阿拉伯文=ar
= 马耳他文=mt
瑞士=CH 意大利文=it
= 丹麦文=da
德国=DE 德文=de
阿尔及利亚=DZ 阿拉伯文=ar
斯洛伐克=SK 斯洛伐克文=sk
立陶宛=LT 立陶宛文=lt
意大利=IT 意大利文=it
爱尔兰=IE 英文=en
新加坡=SG 中文=zh
= 罗马尼亚文=ro
加拿大=CA 英文=en
比利时=BE 荷兰文=nl
= 挪威文=no
= 波兰文=pl
中国=CN 中文=zh
日本=JP 日文=ja
希腊=GR 德文=de
塞尔维亚=RS 塞尔维亚文=sr
= 希伯来文=iw
印度=IN 英文=en
黎巴嫩=LB 阿拉伯文=ar
尼加拉瓜=NI 西班牙文=es
= 中文=zh
马其顿王国=MK 马其顿文=mk
白俄罗斯=BY 白俄罗斯文=be
斯洛文尼亚=SI 斯洛文尼亚文=sl
秘鲁=PE 西班牙文=es
印度尼西亚=ID 印度尼西亚文=in
英国=GB 英文=en

通过该程序就可获得Java所支持的国家/语言环境。

7.7.1 Java国际化的思路

Java程序的国际化思路是将程序中的标签、提示等信息放在资源文件中,程序需要支持哪些国家、语言环境,就对应提供相应的资源文件。

资源文件是key-value对,每个资源文件中的key是不变的,但value则随不同的国家、语言而改变。

java国际化类相关的类

Java程序的国际化主要通过如下三个类完成。

描述
java.util.Resource.Bundle 用于加载国家、语言资源包。
java.util.Locale 用于封装特定的国家区域、语言环境。
java.text.MessageFormat 用于格式化带占位符的字符串。

为了实现程序的国际化,必须先提供程序所需要的资源文件。资源文件的内容是很多key-value对,其中

  • key是程序使用的部分,
  • value则是程序界面的显示字符串

资源文件的命名的命名规则

资源文件的命名可以有如下三种形式:

  1. baseName_language_country.properties
  2. baseName_language.properties
  3. baseName.properties

其中baseName是资源文件的基本名,用户可随意指定;而languagecountry都不可随意变化,必须是Java所支持的语言和国家

7.7 Java9改进的国际化与格式化

什么是国际化

国际化是指应用程序运行时,可根据客户端请求来自的国家/地区、语言的不同而显示不同的界面。例如,

  • 如果请求来自于中文操作系统的客户端,则应用程序中的各种提示信息错误和帮助等都使用中文文字;
  • 如果客户端使用英文操作系统,则应用程序能自动识别,并做出英文的响应。

为什么要国际化

引入国际化的目的是为了提供自适应、更友好的用户界面,并不需要改变程序的逻辑功能。

国际化缩写

国际化的英文单词是Internationalization,因为这个单词太长了,有时也简称I18N,其中:

  • I是这个单词的第个字母,
  • 18表示中间省略的字母个数,
  • 而N代表这个单词的最后一个字母。

本地化

一个国际化支持很好的应用,在不同的区域使用时,会呈现出本地语言的提示。这个过程也被称为Localization,即本地化。类似于国际化可以称为I18N,本地化也可以称为I10N

Java9国际化支持升级到了Unicode8.0字符集,因此提供了对不同国家、不同语言的支持,它已经具有了国际化和本地化的特征及API,因此Java程序的国际化相对比较简单。尽管Java开发工具为国际化和本地化的工作提供了一些基本的类,但还是有一些对于Java应用程序的本地化和国际化来说较困难的工作,例如:消息获取,编码转换,显示布局数字日期货币的格式等。

当然,一个优秀的全球化软件产品,对国际化和本地化的要求远远不止于此,甚至还包括用户提交数据的国际化和本地化。

7.6.2 Java9增加的VarHandle

VarHandle主要用于动态操作数组的元素或对象的成员变量。VarHandleMethodHandle非常相似,它也需要通过MethodHandles来获取实例,接下来调用VarHandle的方法即可动态操作指定数组的元素或指定对象的成员变量

程序示例

下面程序示范了VarHandle的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.lang.invoke.*;
import java.util.Arrays;

class User
{
String name;
static int MAX_AGE;
}
public class VarHandleTest
{
public static void main(String[] args)throws Throwable
{
String[] sa = new String[]{"Java", "Kotlin", "Go"};
// 获取一个String[]数组的VarHandle对象
VarHandle avh = MethodHandles.arrayElementVarHandle(String[].class);
// 比较并设置:如果第三个元素是Go,则该元素被设为Lua
boolean r = avh.compareAndSet(sa, 2, "Go", "Lua");
// 输出比较结果
System.out.println(r); // 输出true
// 看到第三个元素被替换成Lua
System.out.println(Arrays.toString(sa));
// 获取sa数组的第二个元素
System.out.println(avh.get(sa, 1)); // 输出Kotlin
// 获取并设置:返回第三个元素,并将第三个元素设为Swift
System.out.println(avh.getAndSet(sa, 2, "Swift"));
// 看到第三个元素被替换成Swift
System.out.println(Arrays.toString(sa));

// 用findVarHandle方法获取User类中名为name,
// 类型为String的实例变量
VarHandle vh1 = MethodHandles.lookup().findVarHandle(User.class,
"name", String.class);
User user = new User();
// 通过VarHandle获取实例变量的值,需要传入对象作为调用者
System.out.println(vh1.get(user)); // 输出null
// 通过VarHandle设置指定实例变量的值
vh1.set(user, "孙悟空");
// 输出user的name实例变量的值
System.out.println(user.name); // 输出孙悟空
// 用findVarHandle方法获取User类中名为MAX_AGE,
// 类型为Integer的类变量
VarHandle vh2 = MethodHandles.lookup().findStaticVarHandle(User.class,
"MAX_AGE", int.class);
// 通过VarHandle获取指定类变量的值
System.out.println(vh2.get()); // 输出0
// 通过VarHandle设置指定类变量的值
vh2.set(100);
// 输出User的MAX_AGE类变量
System.out.println(User.MAX_AGE); // 输出100
}
}

程序调用MethodHandles类的静态方法可获取操作数组的VarHandle对象,接下来程序可通过VarHandle对象来操作数组的方法,包括比较并设置数组元素、获取并设置数组元素等
VarHandle既可设置实例变量的值,也可获取实例变量的值。
使用VarHandle操作类变量与操作实例变量差别不大,区别只是类变量不需要对象,因此使用VarHandle操作类变量时无须传入对象作为参数。
VarHandleMethodHandle一样,它也是一种动态调用机制,当程序通过Methodhandles.Lookup来获取成员变量时,可根据字符串名称来获取成员变量,这个字符串名称同样可以是动态改变的,因此非常灵活。

7.6 变量处理和方法处理

Java9引入了一个新的VarHandle类,并增强了原有的MethodHandle类。通过这两个类,允许Java像动态语言一样引用变量、引用方法,并调用它们。

7.6.1 Java9增强的MethodHandle

MethodHandleJava增加了方法引用的功能,方法引用的概念有点类似于C的”函数指针”。这种方法引用是一种轻量级的引用方式,它不会检查方法的访问权限,也不管方法所属的类、实例方法或静态方法, MethodHandle就是简单代表特定的方法,并可通过MethodHandle来调用方法。
为了使用MethodHandle,还涉及如下几个类。

描述
MethodHandles MethodHandle的工厂类,它提供了一系列静态方法用于获取MethodHandle
MethodHandles.Lookup Lookup静态内部类也是MethodHandleVarHandle的工厂类,专门用于获取MethodHandleVarHandle
MethodType 代表一个方法类型。 MethodType根据方法的形参、返回值类型来确定方法类型下面程序示范了MethodHandle的用法。

程序示例

下面程序示范了MethodHandle的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.lang.invoke.*;

public class MethodHandleTest
{
// 定义一个private的类方法
private static void hello()
{
System.out.println("Hello world!");
}
// 定义一个private的实例方法
private String hello(String name)
{
System.out.println("执行带参数的hello" + name);
return name + ",您好";
}
public static void main(String[] args) throws Throwable
{
// 定义一个返回值为void、不带形参的方法类型
MethodType type = MethodType.methodType(void.class);
// 使用MethodHandles.Lookup的findStatic获取类方法
MethodHandle mtd = MethodHandles.lookup()
.findStatic(MethodHandleTest.class, "hello", type);
// 通过MethodHandle执行方法
mtd.invoke();

// 使用MethodHandles.Lookup的findVirtual获取实例方法
MethodHandle mtd2 = MethodHandles.lookup()
.findVirtual(MethodHandleTest.class, "hello",
// 指定获取返回值为String, 形参为String的方法类型
MethodType.methodType(String.class, String.class));
// 通过MethodHandle执行方法,传入主调对象和参数
System.out.println(mtd2.invoke(new MethodHandleTest(), "孙悟空"));
}
}

程序使用MethodHandles.Look对象根据类、方法名、方法类型来获取Methodhandle对象。由于此处的方法名只是一个字符串,而该字符串可以来自于变量、配置文件等,这意味着通过MethodHandle可以让Java动态调用某个方法

7.5.2 使用正则表达式

一旦在程序中定义了正则表达式,就可以使用PatternMatcher来使用正则表达式。
Pattern对象是正则表达式编译后在内存中的表示形式,因此,

  • 正则表达式字符串必须先被编译为Pattern对象,
  • 然后再利用该Pattern对象创建对应的Matcher对象。

执行匹配所涉及的状态保留在Matcher对象中,多个Matcher对象可共享同一个Pattern对象。

因此,典型的调用顺序如下:

1
2
3
4
5
//将一个字符串编译成Pattern对象
Pattern p= Pattern.compile("a*b");
//使用 Pattern对象创建 Matcher对象
Matcher m=p.matcher(\"aaaaab\");
boolean b=m.matches();//返回true

定义好的的Pattern对象可以多次重复使用。

直接使用Pattern类的静态matches方法

如果某个正则表达式仅需一次使用,则可直接使用Pattern类的静态matches方法,此方法自动把指定字符串编译成匿名的Pattern对象,并执行匹配。

1
boolean b= Pattern matches("a*b","aaaaab");//true

采用这种语句每次都需要重新编译新的Pattern对象,不能重复利用已编译的Pattern对象,所以效率不高.

Pattern类线程安全

Pattern是不可变类,可供多个并发线程安全使用。

Matcher类常用方法

方法 描述
find() 返回目标字符串中是否包含与Pattern匹配的子串。
group() 返回上一次与Pattern匹配的子串。
start() 返回上一次与Pattern匹配的子串在目标字符串中的开始位置。
end() 返回上一次与Pattern匹配的子串在目标字符串中的结束位置加1。
lookingAt() 返回目标字符串前面部分与Pattern是否匹配。
matches() 返回整个目标字符串与Pattern是否匹配。
reset() 将现有的Matcher对象应用于一个新的字符序列。

CharSequence接口

PatternMatcher类的介绍中经常会看到一个CharSequence接口,该接口代表一个字符序列,其中CharBufferStringStringBufferStringBuilder都是它的实现类。简单地说, CharSequence代表一个各种表示形式的字符串。

通过Matcher类的find()group()方法可以从目标字符串中依次取出特定子串,例如互联网的网络爬虫,它们可以自动从网页中识别出所有的电话号码。

实例

下面程序示范了如何从大段的字符串中找出电话号码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.regex.*;

public class FindGroup {
public static void main(String[] args) {
// 使用字符串模拟从网络上得到的网页源码
String str = "卖豆腐:13500006666" + "交朋友:13611125565" + "出售二手电脑:15899903312";
// 创建一个Pattern对象,并用它建立一个Matcher对象
// 该正则表达式只抓取13X和15X段的手机号,
// 实际要抓取哪些电话号码,只要修改正则表达式即可。
Matcher m = Pattern.compile("((13\\d)|(15\\d))\\d{8}").matcher(str);
// 将所有符合正则表达式的子串(电话号码)全部输出
while (m.find()) {
System.out.println(m.group());
}
}
}

运行结果:

1
2
3
13500006666
13611125565
15899903312

find方法

find()方法依次查找字符串中与Pattern匹配的子串,一旦找到对应的子串,下次调用find()方法时将接着向下查找。
find()方法还可以传入一个int类型的参数,带int参数的find()方法将从该int索引处向下搜索.

开发一个简单网络爬虫的思路

使用正则表达式可以提取网页上的电话号码,也可以提取邮件地址等信息。如果程序再进一步,可以从网页上提取超链接信息,再根据超链接打开其他网页,然后在其他网页上重复这个过程就可以实现简单的网络爬虫了。

start()end()方法主要用于确定子串在目标字符串中的位置.

matches和lookingAt方法的区别

matches()lookingAt()方法有点相似,只是:

  • matches方法要求整个字符串和Pattern完全匹配时才返回true,
  • lookingAt()只要字符串以Pattern开头就会返回true

reset方法

reset()方法可将现有的Matcher对象应用于新的字符序列.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.regex.*;

public class MatchesTest
{
public static void main(String[] args)
{
String[] mails =
{
"kongyeeku@163.com" ,
"kongyeeku@gmail.com",
"ligang@crazyit.org",
"wawa@abc.xx"
};
String mailRegEx = "\\w{3,20}@\\w+\\.(com|org|cn|net|gov)";
Pattern mailPattern = Pattern.compile(mailRegEx);
Matcher matcher = null;
for (String mail : mails)
{
if (matcher == null)
{
// 创建Matcher应用于第一个序列
matcher = mailPattern.matcher(mail);
}
else
{
// 匹配新的序列
matcher.reset(mail);
}
String result = mail + (matcher.matches() ? "是" : "不是")
+ "一个有效的邮件地址!";
System.out.println(result);
}
}
}

从某个角度来看, Matchermatches()lookingAt()String类的equals()startsWith()有点相似。区别是

  • String类的equals()startsWith()都是与字符串进行比较,
  • MatchermatcheslookingAt()则是与正则表达式进行匹配。

String类里也提供了matches方法,该方法返回该字符串是否匹配指定的正则表达式.

还可以利用正则表达式对目标字符串进行分割査找替换等操作.

7.5.1 创建正则表达式

创建正则表达式就是创建一个特殊的字符串。正则表达式所支持的合法字符如表71所示。

正则表达式合法字符

字符 描述
x x可代表任何合法的宇符
\0mnn 八进制数0mnn所表示的字符
\xhh 十六进制值0xhh所表示的字符
\uhhhh 十六进制值0xhhh所表示的Unicode字符
\t 制表符(\u0009)
\n 新行符或者叫换行符(\u000A)
\r 回车符(\u000D)
\f 换页符(\u000C)
\a 报警(bell)符(\u0007)
\e Escape符(\u001B)
\cx x对应的的控制符。例如,\cM匹配CtrI-Mx值必须为大写的A到Z或小写的az之一

正则表达式中的特殊字符

字符 描述
$ 匹配一行的结尾
^ 匹配一行的开头。如果要匹配^字符本身,请使用\^
小括号() 标记子表达式的开始和结束位置。要匹配这些字符,请使用\(\)
中括号[] 用于确定中括号表达式的开始和结束位置。要匹配这些字符,请使用\[\]

……
如果需要匹配这些特殊字符,就必须首先将这些字符转义,也就是在前面添加个反斜线(\).

这篇文章省略,表格有点多,我懒得抄,用到再看书去.

7.5 正则表达式

正则表达式是一个强大的字符串处理工具,可以对字符串进行查找提取分割替换等操作。

String类中提供的正则表达式方法

String类里也提供了如下几个特殊的方法

方法 描述
boolean matches(String regex) 判断该字符串是否匹配指定的正则表达式
String replaceAll(String regex, String replacement) 将该字符串中所有匹配regex的子串替换成replacement
String replaceFirst(String regex, String replacement) 将该字符串中第一个匹配regex的子串替换成replacement
String[] spit(String regex) regex作为分隔符,把该字符串分割成多个子串。

上面这些特殊的方法都依赖于Java提供的正则表达式支持,除此之外,Java还提供了PatternMatcher两个类专门用于提供正则表达式支持。

7.4.3 Java8新增的日期 时间包

java8开始专门新增了一个java.time包,该包下包含了如下常用的类。

时间类 描述
Clock 该类用于获取指定时区的当前日期、时间。该类可取代System类的currentTimeMillis()方法,而且提供了更多方法来获取当前日期、时间。该类提供了大量静态方法来获取Clock对象.
Duration 该类代表持续时间。该类可以非常方便地获取一段时间。
Instant 代表一个具体的时刻,可以精确到纳秒。该类提供了静态的now()方法来获取当前时刻,也提供了静态的now(Clock clock)方法来获取clock对应的时刻。除此之外,它还提供了一系列minusXxx()方法在当前时刻基础上减去一段时间,也提供了plusXxx()方法在当前时刻基础上加上一段时间。
LocalDate 该类代表不带时区的日期,例如2007-12-03。该类提供了静态的now()方法来获取当前日期,也提供了静态的now(Clock clock)方法来获取clock对应的日期。除此之外,它还提供了minusXxx()方法在当前年份基础上减去几年几月几周几日等,也提供了plusXxx()方法在当前年份基础上加上几年、几月、几周或几日等
LocalTime 该类代表不带时区的时间,例如10:15:30。该类提供了静态的now()方法来获取当前时间,也提供了静态的now(Clock clock)方法来获取clock对应的时间。除此之外,它还提供了minusXxx()方法在当前年份基础上减去几小时几分几秒等,也提供了plusXxx()方法在当前年份基础上加上几小时、几分、几秒等。
LocalDateTime 该类代表不带时区的日期、时间,例如2007-12-03T10:15:30。该类提供了静态的now()方法来获取当前日期、时间,也提供了静态的now(Clock clock)方法来获取clock对应的日期、时间。除此之外,它还提供了minusXxx()方法在当前年份基础上减去几年、几月、几日、几小时、几分、几秒等,也提供了plusXxxO方法在当前年份基础上加上几年、几月、几日几小时、几分、几秒等。
MonthDay 该类仅代表月日,例如-04-12。该类提供了静态的now()方法来获取当前月日,也提供了静态的now(Clock clock)方法来获取clock对应的月日。
Year 该类仅代表年,例如2014。该类提供了静态的now()方法来获取当前年份,也提供了静态的now(Clock clock)方法来获取clock对应的年份。除此之外,它还提供了minusYears方法在当前年份基础上减去几年,也提供了plusYears()方法在当前年份基础上加上几年
YearMonth 该类仅代表年月,例如2014-04。该类提供了静态的now()方法来获取当前年月,也提供了静态的now(Clock clock)方法来获取clock对应的年月。除此之外,它还提供了minusXxx()方法在当前年月基础上减去几年、几月,也提供了plusXxx()方法在当前年月基础上加上几年、几月。
ZonedDateTime 该类代表一个时区化的日期、时间.
Zoned 该类代表一个时区。
DayOfWeek 这是一个枚举类,定义了周日到周六的枚举值。
Month 这也是一个枚举类,定义了一月到十二月的枚举值。

程序示例

下面通过一个简单的程序来示范这些类的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.time.*;

public class NewDatePackageTest {
public static void main(String[] args) {
// -----下面是关于Clock的用法-----
// 获取当前Clock
Clock clock = Clock.systemUTC();
// 通过Clock获取当前时刻
System.out.println("当前时刻为:" + clock.instant());
// 获取clock对应的毫秒数,与System.currentTimeMillis()输出相同
System.out.println(clock.millis());
System.out.println(System.currentTimeMillis());
// -----下面是关于Duration的用法-----
Duration d = Duration.ofSeconds(6000);
System.out.println("6000秒相当于" + d.toMinutes() + "分");
System.out.println("6000秒相当于" + d.toHours() + "小时");
System.out.println("6000秒相当于" + d.toDays() + "天");
// 在clock基础上增加6000秒,返回新的Clock
Clock clock2 = Clock.offset(clock, d);
// 可看到clock2与clock1相差1小时40分
System.out.println("当前时刻加6000秒为:" + clock2.instant());
// -----下面是关于Instant的用法-----
// 获取当前时间
Instant instant = Instant.now();
System.out.println(instant);
// instant添加6000秒(即100分钟),返回新的Instant
Instant instant2 = instant.plusSeconds(6000);
System.out.println(instant2);
// 根据字符串中解析Instant对象
Instant instant3 = Instant.parse("2014-02-23T10:12:35.342Z");
System.out.println(instant3);
// 在instant3的基础上添加5小时4分钟
Instant instant4 = instant3.plus(Duration.ofHours(5).plusMinutes(4));
System.out.println(instant4);
// 获取instant4的5天以前的时刻
Instant instant5 = instant4.minus(Duration.ofDays(5));
System.out.println(instant5);
// -----下面是关于LocalDate的用法-----
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
// 获得2014年的第146天
localDate = LocalDate.ofYearDay(2014, 146);
System.out.println(localDate); // 2014-05-26
// 设置为2014年5月21日
localDate = LocalDate.of(2014, Month.MAY, 21);
System.out.println(localDate); // 2014-05-21
// -----下面是关于LocalTime的用法-----
// 获取当前时间
LocalTime localTime = LocalTime.now();
// 设置为22点33分
localTime = LocalTime.of(22, 33);
System.out.println(localTime); // 22:33
// 返回一天中的第5503秒
localTime = LocalTime.ofSecondOfDay(5503);
System.out.println(localTime); // 01:31:43
// -----下面是关于localDateTime的用法-----
// 获取当前日期、时间
LocalDateTime localDateTime = LocalDateTime.now();
// 当前日期、时间加上25小时3分钟
LocalDateTime future = localDateTime.plusHours(25).plusMinutes(3);
System.out.println("当前日期、时间的25小时3分之后:" + future);
// 下面是关于Year、YearMonth、MonthDay的用法示例-----
Year year = Year.now(); // 获取当前的年份
System.out.println("当前年份:" + year); // 输出当前年份
year = year.plusYears(5); // 当前年份再加5年
System.out.println("当前年份再过5年:" + year);
// 根据指定月份获取YearMonth
YearMonth ym = year.atMonth(10);
System.out.println("year年10月:" + ym); // 输出XXXX-10,XXXX代表当前年份
// 当前年月再加5年,减3个月
ym = ym.plusYears(5).minusMonths(3);
System.out.println("year年10月再加5年、减3个月:" + ym);
MonthDay md = MonthDay.now();
System.out.println("当前月日:" + md); // 输出--XX-XX,代表几月几日
// 设置为5月23日
MonthDay md2 = md.with(Month.MAY).withDayOfMonth(23);
System.out.println("5月23日为:" + md2); // 输出--05-23
}
}