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
}

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

  1. pig和cat是同一个引用,指向同一个字符串,但是pig跟dog是两个引用;因为只有使用常量进行初始化的字符串,才会使用常量池里的同一个引用。
  2. +表示字符串拼接时,运算优先级和执行加法是一致的,即 "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);
}

猜你喜欢

转载自www.cnblogs.com/filozofio/p/12356989.html
今日推荐