Java解惑(⼆)puzzle11
上⼀篇记录了,主要是关于表达式的,表达式的⼀个关键就是有值,所以很多的谜题也都围绕着数据类型展开,今天要分享的是字符之谜,⽆论什么编程语⾔,字符总是⼀个很好玩的好题.在之前也总结过java中String的⼀些性能上的问题,发现看过这13个puzzle后⼜加深了⼀些理解吧。
puzzle 11最后的笑声
public class LastLaugh {
public static void main(String args[]) {
System.out.print("H" + "a");
System.out.print('H' + 'a');
}
}
这个问题还是⾮常简单的,第⼀⾏肯定是打印Ha的,但是第⼆⾏就不同了,这是两个字符相加,我们知
道两个字符相加会被提升到int型的相加,所以它实际上是72和97相加。解决的⼀个技巧是 System.out.print(""+'H' + 'a'); 这是把其他数据类型转换为string的⼀个⾮常快捷的⽅式。
puzzle 12 ABC
public class Abc {
public static void main(String[] args) {
String letters = "ABC";
char[] numbers = { '1', '2', '3' };
System.out.println(letters + " easy as " + numbers);
}
}
也许研究过String的同学可能认为打印numbers会理所当然的打印出字符串来,因为StringBuilder这些本⾝也是⽤字符数组来实现的,但是这个例⼦打印的结果是ABC easy as [C@2e6e1408 ,可以看出
后⾯是⼀个对象名。原因是char数组要转换为string的时候要调⽤其toString⽅法,这个⽅法是从Object那⾥继承来的,所以就打印了上⾯的结果。但是上⾯的代码中我们如果直接打印numbers则不会出现这样的问题,原因是System.out.println⽅法对于字符数组参数进⾏了重载使得其可以正常打印数组中包含的内容。
puzzle 13动物庄园
public class AnimalFarm {
public static void main(String[] args) {
final String pig = "length: 10";
final String dog = "length: " + pig.length();
System.out.println("Animals are equal: "
+ pig == dog);
}
}
不想卖关⼦,这个打印的结果就是 false(可能和⼤家想的差很多)。这⾥⾯有两个陷阱,第⼀个就是关于字符串初始化的,这个问题反⽽会迷倒⼀些对于string有研究的同学,因为pig和dog引⽤的字符串内容是相同的,== ⽐较的是引⽤的对象是不是同⼀个(C的思想是⽐较地址),并且根据java string 常量池的特性(),任何String类型的常量表达式,如果指定的是相同的字符串,那么他们就会指向相同的对象,所以我们认为可能结果就是Animals are equal : true了。其实不然,⽤==判断pig和dog会得到false。原因就是dog初始化的时候并⾮是⼀个常量表达式。忽然发现⾃⼰⼀直忽略了这个问题,惭愧。这个⼤家可以⾃⼰写两⾏简单的代码测试⼀下。
那么为什么不会打印“Animals are equal:”呢?原因是操作符优先级的问题,+的优先级⾼于==,所以这个表达式实际⽐较的是“Animals are euqal:length:10”和“length:10”,所以就直接打印了⼀个false了。这个给我们的启⽰就是当⼀个表达式中涉及到多个操作符的时候,我们不确定优先级的时候⼀定要加括号。System.out.println("Animals are equal: "+ (pig == dog));
puzzle 14 转义字符的溃败
public class EscapeRout {
public static void main(String[] args) {
// \u0022 is the Unicode escape for double-quote (")
System.out.println("a\u0022.length() + \u0022b".length());
}
}
背景介绍 \u0022 是unicode对于双引号的表⽰⽅法。这个程序可能有两种结果⼀是把打印的内容当成整个字符串打印,⽽是先把\u0022转义。实际上就是先进⾏转移操作,这个是编译器解析最前完成的。所以程序就变成了System.out.println("a".length() +
"b".length());,。这告诉我们尽量不要⽤unicode转义字符。
puzzle 15 令⼈晕头转向的Hello
/**
* 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
*/
public class Test {
public static void main(String[] args) {
System.out.print("Hell");
System.out.println("o world");
}
}
其实把这段代码直接放在你的eclipse⾥⾯就会发现问题了,报错,⽽其是注释部分。注意第⼆⾏注释⾥⾯有个\u这有标志了unicode转移字符的开始,但是后⾯却不是可识别的16进制数,所以导致程序报错。这个好蛋疼,是么?
问题是很多这样的注释是⾃动⽣成的,和windows下⽬录层级⽤反斜杠表⽰,这就很容易引发问题。
所以哪天注释报错了,那就搜⼀下\u试试吧。
puzzle 16 ⾏打印程序
public class LinePrinter {
public static void main(String[] args) {
// Note: \u000A is Unicode representation of linefeed (LF)
char c = 0x000A;
System.out.println(c);
}
}
这个例⼦也⽐较蛋疼,原因还是在注释⾥⾯,这个还是和unicode转移有关,事实上,在编译器去掉代码中的空⾏和注释之前,unicode的已经被替换为转移字符了,⽽\u000A代表的是换⾏符,所以我们就可以发现问题了,这个注释会被拆成两⾏,⾃然就会报错了,万恶的unicode。
puzzle 17 嗯?
\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020\u0020
\u0063\u006c\u0061\u0073\u0073\u0020\u0055\u0067\u006c\u0079
\u007b\u0070\u0075\u0062\u006c\u0069\u0063\u0020\u0020\u0020
\u0020\u0020\u0020\u0020\u0073\u0074\u0061\u0074\u0069\u0063
\u0076\u006f\u0069\u0064\u0020\u006d\u0061\u0069\u006e\u0028
\u0053\u0074\u0072\u0069\u006e\u0067\u005b\u005d\u0020\u0020
java switch case string\u0020\u0020\u0020\u0020\u0061\u0072\u0067\u0073\u0029\u007b
\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u006f\u0075\u0074
\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u0020
\u0022\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u0022\u002b
\u0022\u006f\u0072\u006c\u0064\u0022\u0029\u003b\u007d\u007d
actually。。这是⼀段可执⾏的代码,作者指向告诉我们,尽。。量。。不。。要。。使。。⽤。。unicdoe
puzzle 18 字符串奶酪
public class StringCheese {
public static void main(String args[]) {
byte bytes[] = new byte[256];
for(int i = 0; i < 256; i++)
bytes[i] = (byte)i;
String str = new String(bytes);
for(int i = 0, n = str.length(); i < n; i++)
System.out.print((int)str.charAt(i) + " ");
}
}
这个程序⾸先将数字转换为byte数组,然后利⽤byte数组⽣成⼀个String,再将string中的每⼀个字符转成int打印出来,正常的思维我们想看到是0--255这些数字,但实际上是不确定的。问题出在定义⼀个新的String的时候,由于⽤bytes定义,并且没有指定字符集,API中提到,数组的长度是字符集的⼀个函数,所以如果没有指定就出现了不确定的字符长度。
很多开发过J2EE同学应该⾮常熟悉另⼀种有byte初始化String的⽅法,就是传⼊第⼆个字符集参数,在⽹站开发的时候经常⽤这个来统⼀编码,特别是中⽂乱码的问题。
puzzle 19漂亮的⽕花
public class Classifier {
public static void main(String[] args) {
System.out.println(
classify('n') + classify('+') + classify('2'));
}
static String classify(char ch) {
if ("0123456789".indexOf(ch) >= 0)
return "NUMERAL ";
if ("abcdefghijklmnopqrstuvwxyz".indexOf(ch) >= 0)
return "LETTER ";
/*
*      (Operators not supported yet)
*      if ("+-*/&|!=".indexOf(ch) >= 0)
*          return "OPERATOR ";
*/
return "UNKNOWN ";
}
这个例⼦的问题,其实很同意看出来了,就是块注释语句的第⼀个/*和代码中的*/进⾏了匹配,导致整个代码就乱了。这是块注释引起的⼀个经典的问题,书中还给了我们提⽰就是块注释是不⽀持嵌套的。这个puzzle中,作者还提到了⼀种程序员喜欢使⽤的注释⽅法,就是讲⼀段代码放在if(false){}的block⾥⾯,这也容易产⽣问题,如果不是为了⼀些调试上的⽅便也不建议使⽤。
puzzle 20 我们的类是什么
package com.javapuzzlers;
public class Me {
public static void main(String[] args) {
System.out.println(
Name().replaceAll(".", "/") + ".class");
}
}
这个例⼦很简单,他的本意是打印出这个类的名字com.javapuzzlers.Me 然后将.替换为/这样就可以获得这个⽂件的的具体⽬录了。但是要注意replaceAll的第⼀个参数是⼀个正则表达式,”.“在正则⾥⾯,相信⼤家也知道表⽰匹配任何字符,这样结果就全变成了//。解决办法有两个,⼀是写正确的正则表达式,也就是"\\."第⼆种⽅法是⽤Patern的quote⽅法,直接表⽰要匹配的内容。
这⾥⾯存在⼀个隐患,及时我们得到想要的结果即com/javapuzzlers/Me.class 那么它也只在unix/linux上有⽤,windows上的⽬录是⽤反斜杠的,所以失效。下⾯的puzzle就会涉及到这个问题。
puzzle 21 我们的类是什么 镜头2
package com.javapuzzlers;
import java.io.File;
public class MeToo {
public static void main(String[] args) {
System.out.println(Name().
replaceAll("\\.", File.separator) + ".class");
}
}
这应该是上⼀个问题的修改版,在windows下会出问题,原因就是File.separator是反斜杠,⽽在这⾥作为替代参数,和普通字符串不同,他要进⾏转移,所以就会发⽣错误。现在的JDK提供了replace⽅法,更加适合处理简单的情况,两个参数均为普通的字符串,省去了很多的问题。
puzzle 22 url的愚弄
public class BrowserTest {
public static void main(String[] args) {
System.out.print("iexplore:");
le;
System.out.println(":maximize");
}
}
事实上,我刚发现java的这个特性,语句标号。C语⾔中的goto就⽤到过语句标号,事实上写到这⾥的时候,我还是不知道java中语句标号的作⽤是什么,以及他为什么这么设计。这个例⼦中,显然http:作为⼀个标号了。后⾯跟⼀⾏注释,所以代码没有任何问题,完全可以执⾏。
puzzle 23 不劳⽽获
import java.util.*;
public class Rhymes {
private static Random rnd = new Random();
public static void main(String[] args) {
StringBuffer word = null;
Int(2)) {
case 1:  word = new StringBuffer('P');
case 2:  word = new StringBuffer('G');
default: word = new StringBuffer('M');
}
word.append('a');
word.append('i');
word.append('n');
System.out.println(word);
}
}
本章最后⼀个puzzle是我最喜欢的,⾥⾯三个陷阱,我只发现了⼀个。那就是我们发现每⼀个case都没有break,所以最后的结果不可能打印Pain和Gain。那么还有两个陷阱,⼀个就是关于⽣成随机数的,nextInt的参数设为2只能⽣成0和1两个随机数,正确的写法是参数为3.
接下来是最好玩的⼀个陷阱,很有意思,那就是最后结果只能打印ain,很奇怪吧,事实上问题出在StringBuffer的初始化
上,StringBuffer没有字符作为参数的构造器,他只有三种构造器⼀是⽆参数的,⽽是接受String的,三是接受int作为初始容量的(),所以这⾥StringBuilder('M'),字符M会被当成int来处理,所以上⾯的语句相当于给StringBuilder知识初始化了容量⽽已。这是⾮常好的⼀个puzzle,⽐前⾯的好玩多了。
Chapter3关注的是字符之谜,其中四个puzzle涉及到了unicode转移字符引起的问题,还有就是char和String之间的⼀些问题,最后⼀个例⼦是受益最深的,尤其是初始化StringBuilder那⾥,给我们提了醒。不难发现,好的编程习惯能够帮助我们避免很多问题,读puzzle,变得更聪明。下⼀章是循环之谜,会更好玩。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。