目录
一、关于split
1.split方法简介
public String[] split(String regex) {
return split(regex, 0);
}
split方法:通过输入的正则表达式,对给定的字符串进行划分,并将划分后的结果通过一个字符串数组返回。
public static void main(String[] args) {
String context = "helloBworld";
String[] result = context.split("B");
for (String str :
result) {
System.out.println(str);
}
}
运行结果如下
hello
world
但如果我们修改进行划分的字符串
public static void main(String[] args) {
String context = "BhelloBworldBBB";
String[] result = context.split("B");
for (String str :
result) {
System.out.println(str);
}
}
运行结果如下
hello
world
我们发现,划分后字符串数组首位出现了一个空串。对于split的划分有三种特殊情况:
- 分隔字符串开头有分隔符
- 分隔字符串中有连续的分隔符
- 分割字符串以分隔符结尾
其对应的划分结果为:
- 字符串数组开头为一个空串
- 两个分隔符中间划分出一个空串
- 字符串数组末尾是空串
按照上面划分特殊情况的结果,第二个例子应该输出为:
""
hello
world
""
""
""
为方便观察,我们讲空串以""形式表示。和实际输出结果比较,后面三个空串并没有被划分到字符串数组中。下面我们引入带参数的split方法进行解释。
2.带数的split方法
观察源代码:
public String[] split(String regex, int limit)
我们发现输入参数多了一个limit,阅读split方法的spec得知,其功能如下:
- limit = n(n > 0):
If the limit n is greater than zero then the pattern will be applied at most n - 1 times, the array's length will be no greater than n, and the array's last entry will contain all input beyond the last matched delimiter.
其大致意思是如果limit为n,n大于0,那么划分时讲使用正则表达式匹配最多n-1次,划分的数组长度不会超过n,数组的最后一个元素将包含除最后匹配的分隔符之外的所有输入。下面我们通过几个例子进行解释。我们以前文的代码为基础,通过改变limit的值观察结果。
public static void main(String[] args) {
String context = "BhelloBworldBBB";
String[] result = context.split("B");
for (String str :
result) {
System.out.println(str);
}
}
例1:limit = 10 ,大于6(按前文对特殊情况的处理得到的数组长度)
运行结果如下:
""
hello
world
""
""
""
可以看到,字符串按我们的理解全部划分。
例2:limit = 4 < 6
运行结果如下:
""
hello
world
BB
其逻辑为首先划分字符串开头的分隔符得到空串,然后划分hello和world,这时候已经划分出三个,那么最后一个元素将包含出除了最后匹配的分隔符(即倒数第三个B)外的全部,即BB。
例3:limit = 2 < 6
运行结果如下:
""
helloBworldBBB
最后匹配的分隔符为顺数第一个B。
- limit = n(n > 0):
If the limit is zero then the pattern will be applied as many times as possible, the array can have any length, and trailing empty strings will be discarded.
其大致意思是,如果limit为零,则将尽可能多地使用分隔符进行匹配,数组可以是任何长度,并且将舍弃最后的空字符串。如果split不加参数,则默认使用limit为0的方法。具体例子可以参照前文,这里不再赘述。
- limit < 0:
If the limit is negative then the pattern will be applied as many times as possible and the array can have any length.
其功能与limit = 0相同,出货了不会舍弃最后的空字符串。
例:limit = -1(为负数即可)
运行结果如下:
""
hello
world
""
""
""
3.避免划分后数组开头出现空串的一个思路
在Poetic Walks问题中,采用"[ \n]+",以换行符和空格进行划分,当输入的第一个字符为空格或者换行符时,划分出的字符数组第一个元素为空字符串,这一点,在前面的例子中也可以看出。为了避免出现空字符串,一个解决问题的思路是在需要进行划分的字符串前面加一个不是分隔符的符号和一个分隔符,然后再调用arraycopy方法去除该符号即可。
private String[] formatInput(String input) {
input = "a " + input;
String[] myLine = input.split("[ \n]+");
String[] formatInput = new String[myLine.length-1];
System.arraycopy(myLine, 1, formatInput, 0, myLine.length - 1);
checkRap();
return formatInput;
}
二、正则表达式判断字词
1.unicode类别
在在Poetic Walks问题中,判断划分后的字符串是否是一个词语,我的依据是词语由标点符号,下划线,数字和字母和连字符组成。对于下划线,数字和字母,我们可以用\\w来进行匹配。对于标点符号,起初我想的是用枚举来对其进行匹配。但考虑标点符号数目不少,同时还有中英文的区别,在网上搜索时,发现一个更好的匹配方式是采用unicode类别。下文是微软对unicode类别的注释。
Unicode 标准为每个常规类别分配一个字符。 例如,特定字符可以是大写字(由
Lu
类别表示),十进制数字(Nd
类别)、数学符号(Sm
类别)或段落分隔符(Zl
类别)。 Unicode 标准中的特定字符集也占据连续码位的特定区域或块。 例如,可在 \u0000 和 \u007F 之间找到基本拉丁字符集,并可在 \u0600 和 \u06FF 之间找到阿拉伯语字符集。正则表达式构造
\p{
name}
\p{
其中,关于标点的字符集如下:
连接符 Pc
,短划线 Pd
,引号 Pi
,引回 Pf
,开始 Ps
,结束 Pe
,其他 Po
2.构造正则表达式
我们可以写出匹配的字符串为(不一定准确):
"^[\\p{Ps}\\p{Pi}]?[\\w-]+[\\p{Pe}\\p{Pf}\\p{Po}]?$"
三、关于复用
1.复用的好处
对于某些问题,针对其存在的一些共性的特点,我们设计出复用性高的代码能够大大减少解决相关问题的代码量,提高编写程序的效率,同时也能够提高软件的质量,便于维护。以本次实验为例,我们通过编写Graph接口和其对应的两个实现类,在完成FriendshipGraph和GraphPoet时,只需要调用对应的方法来实现相应功能即可,在代码出现问题或者需要修改时,只需要修改对应实现类的方法,而不用在FriendshipGraph和GraphPoet两个类中进行修改,大大减少了工作量。同时我们注意到,Graph接口中有一个empty方法,在实验中通过该方法创建一个图对象,而不是直接new 一个对应的实现类的好处是:我们不用将Graph接口的实现类暴露出去,同时,当我们需要更换实现类的时候,不用修改客户端代码。
2.LSP原则
- 子类型可以增加方法,但不可删除
- 子类型需要实现抽象类型中的所有未实现的方法
- 必须有相同类型的返回值或者符合co-variance的返回值
- 子类型中重写的方法必须使用同样类型的参数或者符合co-variance的参数
- 子类型中重写的方法不能抛出额外的异常,抛出相同或者符合co-variance的异常
- 对于spec,应该具有更强的不变量,更弱的前置条件和更强的后置条件