java解惑 谜题10_Java解惑(2)-- 字符谜题

Java解惑(2)-- 字符谜题

11 字符串拼接

static void lastLaugh(){

System.out.println("H" + "a"); //+ 执行字符串拼接 输出Ha

System.out.println('H' + 'a'); //+ 执行加法运算 等价于72+97 输出169

System.out.println("" + 'H' + 'a'); //+ 执行字符串拼接 输出Ha

}

12 字符数组

Java对对象引用的字符串转化定义如下:

如果引用为null,它被转化为字符串"null";

否则转换结果就是对象的toString()方法;

如果toString()方法的结果为null,就用字符串"null"代替

static void ABC() {

String letters = "ABC";

char[] numbers = {'1', '2', '3'};

System.out.println(letters + " easy as " + numbers); //输出诸如 ABC easy as [C@7ea987ac

//要想将一个 char 数组转换成一个字符串,就要调用 String.valueOf(char[])方法

System.out.println(letters + " easy as " + String.valueOf(numbers)); //输出ABC easy as 123

}

上面的代码中,会对char数组调用toString()方法。数组是从Object那里继承的toString()方法,规范中描述道:

返回一个字符串,它包含了该对象所属类的名字,'@'符号,以及表示对象散列码的一个无符号十六进制整数

有关Class.getName的规范描述道:

在char[]类型的类对象上调用该方法的结果为字符串"[C"

连在一起就形成了诸如“[C@7ea987ac”这样的结果

13 动物庄园

static void animalFarm() {

final String pig = "length: 10"; //字符串"length: 10"

final String cat = "length: " + 10; //字符串"length: 10"

final String dog = "length: " + pig.length(); //字符串"length: 10"

System.out.println(pig == cat); //输出true

System.out.println(pig == dog); //输出false

System.out.println("Animals are equal:" + pig == dog); //只输出false

System.out.println("Animals are equal:" + (pig == dog)); //输出Animals are equal:false

}

上面的程序反映了两个问题:

pig和cat是同一个引用,指向同一个字符串,但是pig跟dog是两个引用;因为只有使用常量进行初始化的字符串,才会使用常量池里的同一个引用。

当+表示字符串拼接时,运算优先级和执行加法是一致的,即 "Animals are equal:" + pig == dog等价于("Animals are equal:" + pig) == dog

建议:写代码时不要依赖String的常量池机制,永远把它当做一个对象。除非确实要判断两个对象是否同一个引用,否则永远使用equals()方法比较是否相等。

14 转义字符

Java不会对字符串字面常量里的Unicode转义字符做任何特殊处理,编译器在程序解析成各种符号之前,先将Unicode转义字符转换为它们所表示的字符

static void escapeRout() {

//\u0022是双引号的Unicode转义字符

System.out.println("a\u0022.length()+ \u0022b".length()); //输出2

//上述代码中,括号内的部分等价于 "a".length()+"b".length();

//如果确实想在字符串字面量内部添加双引号,应该使用转移字符序列,转义字符序列是在程序解析成各种符号之后处理的

System.out.println("a\".length() + \"b".length()); //输出16

}

15 令人晕头转向的hello

以下代码无法通过编译,编译器会提示“非法的Unicode转义”

/**

* Generated by the IBM IDL-to-Java compiler, version 1.0

* from F:\TestRoot\apps\a1\units\include\PolicyHome.idl

* Wednesday, June 17, 1998 6:44:40 o’clock AM GMT+00:00

*/

static void test15(){

System.out.println("hell");

System.out.println("o world");

}

问题处在第3行注释中的\units。以反斜杠 \ 并紧跟u开头的会被认为是转义字符的开始,然而后面并没有跟4个十六进制数字,这个Unicode转义字符被认为是病构的,编译器拒绝该程序。

因此即使在注释中,也要注意避免病构的转义字符。

16 Unicode换行

下面的程序无法通过编译,参考上一节,\u000A被当作换行,于是后面的内容就不会被当做注释的部分,。

static void linePrinter(){

// Note: \u000A is Unicode representation of linefeed (LF) char c = 0x000A;

char c = 0x000A;

System.out.println(c);

}

总结14、15、16的教训:使用Unicode转义字符很容易引发混乱,不要使用。

17、18、19略

20 我的类是什么(1)

以下代码希望实现获取当前类的完整类名,并把.替换成/。最后输出"com/javapuzzler/Me.class"

package com.javapuzzler;

public class Me {

public static void main(String[] args) {

String s = Me.class.getName().replaceAll(".","/") + ".class";

System.out.println(s); //输出 //.class

}

}

String.replaceAll的第一个参数接收的是一个正则表达式,而非字符串字面量。正则表达式.可以匹配任意单个字符,因此类名中的每个字符都被替换成了/。

如果想匹配句号,需要在前面添加反斜杠进行转义。又由于反斜杠在字符串字面量中有特殊含义,表示转义字符序列的开始,反斜杠又需要另一个反斜杠转义。

package com.javapuzzler;

public class Me {

public static void main(String[] args) {

String s = Me.class.getName().replaceAll("\\.","/") + ".class";

System.out.println(s); //输出 com/javapuzzler/Me.class

}

}

为了解决这类问题,JDK5提供了一个静态方法java.util.regex.Pattern.quote,它接受一个字符串s作为参数,返回一个字符串s1,s1可用于创建一个与s匹配的模式。

package com.javapuzzler;

public class Me {

public static void main(String[] args) {

String p = Pattern.quote(".");

String s = Me.class.getName().replaceAll(p,"/") + ".class";

System.out.println(s); //输出 com/javapuzzler/Me.class

}

}

该程序的另一个问题是,不是所有文件系统都使用斜杠来分隔文件层次。UNIX系统使用斜杠,而Windows系统使用的是反斜杠,参考谜题21。

21 我的类是什么(2)

以下代码为了兼容不同的操作系统,使用了File.separator来替代斜杠。

public class Me {

public static void main(String[] args) {

String p = Pattern.quote(".");

String s = Me.class.getName().replaceAll(p, File.separator) + ".class";

System.out.println(s);

}

}

但是如果在Windows系统上运行,仍然无法达到预期效果。在Windows系统中,File.separator是个反斜杠。replaceAll方法的第二个参数不是普通的字符串,而是替代字符串。在替代字符串中,反斜杠会被认为是转义字符的开头。

上面的场景可以使用String.replace(CharSequence, CharSequence)方法替代,它做的事情和String.replaceAll相同,但是它将模式和替代字符串都当作字面含义的字符串处理。

public class Me {

public static void main(String[] args) {

String s = Me.class.getName().replace(".", File.separator) + ".class";

System.out.println(s);

}

}

22 URL的愚弄

以下代码可以正常运行,并输出 iexplore::maximize

static void browserTest(){

System.out.print("iexplore:");

http://www.google.com;

System.out.println(":maximize");

}

方法的第2行是一个Java语言中不太常用的特性Labeled Statements。这里http被认为是Labeled Statements的标识符。www.google.com被认为是注释。

建议使用 Labeled Statements 时,要对代码进行正确的格式化。例如

static void browserTest() {

System.out.print("iexplore:");

http:

//www.google.com;

System.out.println(":maximize");

}

23 不劳而获

static void rhymes(){

Random rnd = new Random();

StringBuilder word = null;

switch (rnd.nextInt(2)){

case 1: word = new StringBuilder('P');

case 2: word = new StringBuilder('G');

default: word = new StringBuilder('M');

}

word.append('a').append('i').append('n');

System.out.println(word);

}

我们期望以上程序每次运行,都能以相同的概率输出"Pain","Gain","Main"。但是程序永远只会输出"ain"。这个程序有3个bug。

第一,查看Random.next的方法注释:

the next pseudorandom, uniformly distributed int value between zero (inclusive) and bound (exclusive) from this random number generator's sequence

即返回一个伪随机的、均等分布的int数值,数值范围是0(包括0)到指定数值(不包括)之间。所以rnd.nextInt(2)的值只可能是0,1。

第二,switch语句的每个分支后面漏了break,因此 word = new StringBuilder('M') 总是会被执行。

第三,StringBuilder类并没有接收char类型参数的构造函数,当传入一个char类型参数时,调用的是接收int类型参数的构造函数,即

public StringBuilder(int capacity) {

super(capacity);

}

以下是修改后的程序

static void rhymes(){

Random rnd = new Random();

StringBuilder word = null;

switch (rnd.nextInt(3)){

case 1: word = new StringBuilder("P");break;

case 2: word = new StringBuilder("G");break;

default: word = new StringBuilder("M");

}

word.append('a').append('i').append('n');

System.out.println(word);

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值