首页 > 编程语言 >《java解惑》——续字符串之谜

《java解惑》——续字符串之谜

时间:2023-11-08 16:04:17浏览次数:37  
标签:java String class static Test 解惑 public 之谜


1.字符串替换:

问题:

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


1. package com.javapuzzlers;  
2.   
3. public class Test {  
4.   
5. public static void main(String[] args){  
6. class.getName().replaceAll(".", "/") + ".class");  
7.     }  
8. }

原本期望输出的结果是:com/javapuzzlers/test.class,但是程序运行真正的输出是:/.class。

原因:

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

结论:

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

方法一:

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

1. package com.javapuzzlers;  
2.   
3. public class Test {  
4.   
5. public static void main(String[] args){  
6. class.getName().replaceAll("\\.", "/") + ".class");  
7.     }  
8. }

方法二:

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

1. package com.javapuzzlers;  
2.   
3. import java.util.regex.Pattern;  
4.   
5. public class Test {  
6.   
7. public static void main(String[] args){  
8. class.getName().replaceAll(Pattern.quote("."), "/") + ".class");  
9.     }  
10. }


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

问题:

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

1. package com.javapuzzlers;  
2.   
3. import java.io.File;  
4. import java.util.regex.Pattern;  
5.   
6. public class Test {  
7.   
8. public static void main(String[] args){  
9. class.getName().replaceAll(Pattern.quote("."), File.separator) + ".class");  
10.     }  
11. }


经测试,改程序在Unix/Linux平台上运行正常,打印出com/javapuzzlers/test.class,而在Windows平台上我们原本期望输出com\javapuzzlers\Test.class,但是报如下运行时异常:

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 1
at java.lang.String.charAt(String.java:658)
at java.util.regex.Matcher.appendReplacement(Matcher.java:762)
at java.util.regex.Matcher.replaceAll(Matcher.java:906)
at java.lang.String.replaceAll(String.java:2162)
at com.javapuzzlers.Test.main(Test.java:9)

分析:

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

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

方法一:

使用java.util.regex.Matcher.quoteReplacement方法将字符串转换成相应的替代字符串,代码如下:

1. package com.javapuzzlers;  
2.   
3. import java.io.File;  
4. import java.util.regex.Matcher;  
5. import java.util.regex.Pattern;  
6.   
7. public class Test {  
8.   
9. public static void main(String[] args){  
10. class.getName().replaceAll(Pattern.quote("."), Matcher.quoteReplacement(File.separator)) + ".class");  
11.     }  
12. }

方法二:

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

1. package com.javapuzzlers;  
2.   
3. import java.io.File;  
4.   
5. public class Test {  
6.   
7. public static void main(String[] args){  
8. class.getName().replace(".", File.separator) + ".class");  
9.     }  
10. }


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

1. package com.javapuzzlers;  
2.   
3. import java.io.File;  
4.   
5. public class Test {  
6.   
7. public static void main(String[] args){  
8. class.getName().replace('.', File.separatorChar) + ".class");  
9.     }  
10. }

3.另类诡异的标号:

问题:

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


1. public class Test {  
2.   
3. public static void main(String[] args){  
4. "iexplore:");  
5. //www.google.com  
6. ":maximize");  
7.     }  
8. }


改程序可以正常通过编译,输出结果为:iexplore::maximize。

原因:

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

结论:

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


1. public class Test {  
2.   
3. public static void main(String[] args) {  
4. "iexplore:");  
5. // www.google.com  
6. ":maximize");  
7.     }  
8. }


4.字符串拼接:

问题:

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

1. import java.util.Random;  
2.   
3. public class Test {  
4.       
5. private static Random rnd = new Random();  
6.   
7. public static void main(String[] args) {  
8. null;  
9. switch(rnd.nextInt(2)){  
10. case 1: word = new StringBuffer('P');  
11. case 2: word = new StringBuffer('G');  
12. default: word = new StringBuffer('M');  
13.         }  
14. 'a');  
15. 'i');  
16. 'n');  
17.         System.out.println(word);  
18.     }  
19. }


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

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

原因:

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

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

1. import java.util.Random;  
2.   
3. public class Test {  
4.       
5. private static Random rnd = new Random();  
6.   
7. public static void main(String[] args) {  
8. null;  
9. switch(rnd.nextInt(3)){  
10. case 1: word = new StringBuffer("P");  
11. break;  
12. case 2: word = new StringBuffer("G");  
13. break;  
14. default: word = new StringBuffer("M");  
15. break;  
16.         }  
17. 'a');  
18. 'i');  
19. 'n');  
20.         System.out.println(word);  
21.     }  
22. }


方法二,精简版:

1. import java.util.Random;  
2.   
3. public class Test {  
4.       
5. private static Random rnd = new Random();  
6.   
7. public static void main(String[] args) {  
8. "PGM".charAt(rnd.nextInt(3)) + "ain");  
9.     }  
10. }


方法三:

1. import java.util.Random;  
2.   
3. public class Test {  
4.       
5. private static Random rnd = new Random();  
6.   
7. public static void main(String[] args) {  
8. "Main", "Pain", "Gain"};  
9.         System.out.println(randomElement(a));  
10.     }  
11.       
12. private static String randomElement(String[] a){  
13. return a[rnd.nextInt(a.length)];  
14.     }  
15. }

标签:java,String,class,static,Test,解惑,public,之谜
From: https://blog.51cto.com/u_809530/8255951

相关文章

  • java枚举使用
    Java中的枚举类型采用关键字enum来定义,从jdk1.5才有的新类型,所有的枚举类型都是继承自Enum类型。要了解枚举类型,建议大家先打开jdk中的Enum类简单读一下,这个类里面定义了很多protected方法,比如构造函数,如果要使用这些方法我们可以把枚举类型定义到当前......
  • Linux安装Java环境变量及配置分配用户权限
    1wget安装yum-yinstallwget2.下载wget--no-cookies--no-check-certificate--header"Cookie:gpw_e24=http%3A%2F%2Fwww.oracle.com%2F;oraclelicense=accept-securebackup-cookie""http://download.oracle.com/otn-pub/java/jdk/8u141-b15/336fa29ff2bb......
  • 《java解惑》--表达式之谜
    《java解惑》是Google公司的首席Java架构师JoshuaBloch继《Effectivejava》之后有一力作,专门揭示了很多java编程中意想不到的疑惑,很多有多年工作经验的java开发人员在看完本书之后甚至都怀疑自己会不会写java程序,本系列博客主要记录在读《java解惑》中的经典例子以及原因分析。1......
  • Java中用引号创建String对象和用构造函数的区别
    创建一个String对象一般有以下两种方式:Stringstr1="abcd";Stringstr2=newString("abcd");这两种方式有什么区别呢?我们可以通过下面两个小例子来说明.Example1:Stringa="abcd";Stringb="abcd";System.out.println(a==b);//True......
  • JAVA Date 时间与时间相差计算
    JAVADate时间与时间相差计算Datedate=newDate("2014/1/1018:20");Datedate2=newDate("2014/1/113:5");longtemp=date2.getTime()-date.getTime();//相差毫秒数longhours=temp/1000/3600;//相差小时......
  • 非严格模式下JavaScript语句中“this”默认指向全局对象(window)
    请阅读以下代码varobj={};obj.log=console.log;obj.log.call(console,this);该代码在浏览器中执行,输出的日志结果是什么?obj.log.call(console,this)=console.log(this)。this这里指window,所以最后的表达式是console.log(window)这道题看似在考this的绑定问题,实际......
  • JavaScript权威基础语法教程讲解大全
    JavaScriptJS基础权威语法教程讲解大全https://developer.mozilla.org/zh-CN/docs/Web/JavaScript参考、来源:《爬虫7期:爬虫&逆向7期-第1章-爬虫&逆向7期-1.32-javascript入门_02.mp4》2:28:40......
  • Java中的传值和传引用
    三年以前读研究生的时候,就因为传值和传引用的问题给自己挖过一个坑情景是,我将matlab代码翻译到java中最后计算的结果偏差超过了限差的范围,也超过了java中数据截断误差的范围。经过最后的排查发现,在计算的过程中,一个不应该改变的值被我以引用的形式传入了方法中,方法内部对值进行......
  • 七、Java集合
    一、集合概述集合的特点如下:动态大小:集合可以根据需要动态调整大小,不像数组需要提前指定大小。灵活性:集合提供了各种不同类型的数据结构和容器,例如列表、集、映射、队列等,以满足不同的存储和操作需求。高效性:Java集合框架中的实现类经过优化,提供高效的插入、删除和查找......
  • JAVA遍历list是对其操作
    @Testpublicvoidremove(){ArrayList<String>list=newArrayList<>();list.add("php");list.add("java");list.add("php");list.add("php");list.add(......