《java解惑》读书笔记3——更多字符串之谜

时间:2025-02-15 20:31:49

1.字符串替换:

问题:

下面这段程序把类全路经名中的"."替换为"/",代码如下:

package ;

public class Test {

	public static void main(String[] args){
		(().replaceAll(".", "/") + ".class");
	}
}
原本期望输出的结果是:com/javapuzzlers/,但是程序运行真正的输出是:/.class。

原因:

方法接受一个正则表达式作为它的第一个参数,而并非一个字符序列字面常量,正则表达式"."可以匹配任意单个字符,因此类名中的每一个字符都被替换成了斜杠,也就是我们意想不到的结果。

结论:

解决这个问题有两个方法:

方法一:

在正则表达式中的句点前面添加一个反斜杠"\"进行转义,由于反斜杠在正则表达式中表示转义字符的开始,因此反斜杠自身也必须使用另一个反斜杠来转义,代码如下:

package ;

public class Test {

	public static void main(String[] args){
		(().replaceAll("\\.", "/") + ".class");
	}
}
方法二:

JDK5之后引入了方法,用于接受一个字符串作为参数,并可以添加必需的转义字符,从而将其返回一个正则表达式字符串,该字符串将精确匹配输入的字符串,代码如下:

package ;

import ;

public class Test {

	public static void main(String[] args){
		(().replaceAll(("."), "/") + ".class");
	}
}


2.跨平台的字符串替换:

问题:

第一个例子中我们把类名全路经中的句点替换为Unix/Linux文件路径斜杠,但是如果在Windows操作系统中,文件的分隔是反斜杠,因此上述程序不具有跨平台性,在JDK的被定义为一个公有域,用于指定操作系统平台相关的文件路径分隔符(Unix/Linux中是斜杠,Windows中是反斜杠),因此我们程序修改一下,使用来写出跨平台的程序如下:

package ;

import ;
import ;

public class Test {

	public static void main(String[] args){
		(().replaceAll(("."), ) + ".class");
	}
}
经测试,改程序在Unix/Linux平台上运行正常,打印出com/javapuzzlers/,而在Windows平台上我们原本期望输出com\javapuzzlers\,但是报如下运行时异常:

Exception in thread "main" : String index out of range: 1
at (:658)
at (:762)
at (:906)
at (:2162)
at (:9)

分析:

在Windows平台之所以出现运行时异常,是因为方法的第二个参数不是一个普通的字符串,而是一个替代字符串,在规范中规定,在替代字符串中出现的反斜杠会把紧随其后的字符进行转义,从而导致其被按字面含义处理了。

在JDK5之后,有两种方法解决该问题:

方法一:

使用方法将字符串转换成相应的替代字符串,代码如下:

package ;

import ;
import ;
import ;

public class Test {

	public static void main(String[] args){
		(().replaceAll(("."), ()) + ".class");
	}
}
方法二:

使用方法替代方法,这两个方法功能相同,不同之处在于replace方法接受的两个参数都当作字面含义字符串处理,而非正则表达式,代码如下:

package ;

import ;

public class Test {

	public static void main(String[] args){
		(().replace(".", ) + ".class");
	}
}

如果使用的是JDK5之前的老版本JDK,则可以使用(char, char)方法来解决这一问题,代码如下:

package ;

import ;

public class Test {

	public static void main(String[] args){
		(().replace('.', ) + ".class");
	}
}


3.另类诡异的标号:

问题:

下面的程序能否通过编译,输出结果是什么:

public class Test {

	public static void main(String[] args){
		("iexplore:");
		
		(":maximize");
	}
}
改程序可以正常通过编译,输出结果为:iexplore::maximize。

原因:

咋一看在程序中添加了一句十分诡异的“”让很多人琢磨不定,该URL的前面部分“http:”被当作了java语言中内置的标号(没有goto语句,用于标识跳转位置的标号),后面部分被当作了单行注释。

结论:

很多人可能在java编程中极少碰到标号,但要记得有这样一个java语法特性,如果想要使的程序看起来更加容易理解,最好把程序格式化,将标号和注释分开,代码如下:

public class Test {

    public static void main(String[] args) {
        ("iexplore:");
    http:   // 
        (":maximize");
    }
}

4.字符串拼接:

问题:

猜猜下面程序的打印输出结果:

import ;

public class Test {
    
    private static Random rnd = new Random();

    public static void main(String[] args) {
        StringBuffer word = null;
        switch((2)){
            case 1: word = new StringBuffer('P');
            case 2: word = new StringBuffer('G');
            default: word = new StringBuffer('M');
        }
        ('a');
        ('i');
        ('n');
        (word);
    }
}

有人觉得可能该程序在多次运行中,以相等的概率分别打印输出Pain,Gain和Main,也有可能任务switch的case穿透,因此该程序应该总打印输出Main。

改程序的真实运行结果总是令人奇怪的ain。

原因:

之所以出现这种令人惊异的运行结果是因此改程序总共有3个bug,这3个bug碰巧凑到一块引发令人惊异的结果。

第一个bug:选取的伪随机数使的switch语句只能达到其三种情况中的两种,(int)的规范描述返回一个伪随机数均匀分布在从0(包括)到指定数值(不包括)之间的一个int数值,因此(2)取值只能为0和1,不可能为2,因此switch的2分支永远不可能执行,若想要达到2,则必须将伪随机数修改为(3)。

第二个bug:switch的case语句没有break,因此总会穿透顺序执行到default语句,即总会执行word = new StringBuffer('M');覆盖前面的程序赋值。

第三个bug:StringBuffer根本没有StringBuffer(char)构造函数,StringBuffer只有如下三个构造函数:

(1).默认无参数构造函数:StringBuffer();

(2).指定字符串初始缓冲区内容的构造函数:StringBuffer(String);

(3).指定字符串初始缓冲区初始容量的构造函数:StringBuffer(int);

当使用word = new StringBuffer('M');时,编译器会将字符M自动类型转换为int数值77,从而选择第三个构造函数,因此改程序总打印输出ain也就不难理解了。

结论:

有三种办法修改上述程序:

方法一,修改程序bug:

import ;

public class Test {
    
    private static Random rnd = new Random();

    public static void main(String[] args) {
        StringBuffer word = null;
        switch((3)){
            case 1: word = new StringBuffer("P");
            break;
            case 2: word = new StringBuffer("G");
            break;
            default: word = new StringBuffer("M");
            break;
        }
        ('a');
        ('i');
        ('n');
        (word);
    }
}
方法二,精简版:

import ;

public class Test {
    
    private static Random rnd = new Random();

    public static void main(String[] args) {
        ("PGM".charAt((3)) + "ain");
    }
}
方法三:

import ;

public class Test {
    
    private static Random rnd = new Random();

    public static void main(String[] args) {
        String[] a = {"Main", "Pain", "Gain"};
        (randomElement(a));
    }
    
    private static String randomElement(String[] a){
        return a[()];
    }
}