Java100例常见错误及解决方案

经常编写 Java 程序,经常有一些细节大家可能没怎么注意,这里整理、收集汇总了一些我们编程中常见的问题。供大家在学习中参考。
范例 001:Abstract method 必须以分号结尾,且不带花括号
abstractclassName{
privateStringname;
publicabstractbooleanisStupidName(Stringname){}
}
这有何错误?
答案:错。Abstract method 必须以分号结尾,且不带花括号。
范例 002:局部变量前不能放置任何访问修饰符
publicclassSomething{
voiddoSomething(){
privateStrings=“”;
intl=s.length();
}
}
有错吗?
答案:错。局部变量前不能放置任何访问修饰符(private,public,和 protected)。final 可以用来修饰局部变量
(final 如同 abstract 和 strictfp,都是非访问修饰符,strictfp 只能修饰 class 和 method 而非 variable)。
范例 003:abstract 的 methods 不能以 private 修饰
abstractclassSomething{
privateabstractStringdoSomething();
}
这好像没什么错吧?
答案:错。abstract 的 methods 不能以 private 修饰。abstract 的 methods 就是让子类
implement(实现)具体细节的,怎么可以用 private 把 abstract
method 封锁起来呢?(同理,abstractmethod 前不能加 final)。
范例 004:intx 被修饰错误
publicclassSomething{
publicintaddOne(finalintx){
2 / 54
return++x;
}
}
这个比较明显。
答案:错。intx 被修饰成 final,意味着 x 不能在addOnemethod 中被修改。
范例 005:关于 final 的问题
publicclassSomething{
publicstaticvoidmain(String[]args){
Othero=newOther();
newSomething().addOne(o);
}
publicvoidaddOne(finalOthero){
o.i++;
}
}
classOther{
publicinti;
}
和上面的很相似,都是关于 final 的问题,这有错吗?
答案:正确。在 addOnemethod 中,参数 o 被修饰成 final。如果在 addOnemethod 里我们
修改了 o 的 reference
(比如:o=newOther()😉,那么如同上例这题也是错的。但这里修改的是 o 的 membervairable
(成员变量),而 o 的 reference 并没有改变。
范例 006:易错举例
classSomething{
inti;
publicvoiddoSomething(){
System.out.println(“i=”+i);
}
}
有什么错呢?看不出来啊。
答案:正确。输出的是"i=0"。inti 属於 instantvariable(实例变量,或叫成员变量)。
instantvariable 有 defaultvalue。int 的 defaultvalue 是 0。
范例 007:多用了一个 final
classSomething{
finalinti;
3 / 54
publicvoiddoSomething(){
System.out.println(“i=”+i);
}
}
和上面一题只有一个地方不同,就是多了一个 final。这难道就错了吗?
答案:错。finalinti 是个 final 的 instantvariable(实例变量,或叫成员变量)。final 的
instantvariable 没有 defaultvalue,必须在 constructor(构造器)结束之前被赋予一个明确的值。
可以修改为"finalinti=0;“。
范例 008:main 里 calldo Something 使用错误
publicclassSomething{
publicstaticvoidmain(String[]args){
Somethings=newSomething();
System.out.println(“s.doSomething()returns”+doSomething());
}
publicStringdoSomething(){
return"Dosomething…”;
}
}
看上去很完美。
答案:错。看上去在 main 里 calldoSomething 没有什么问题,毕竟两个 methods 都在同一
个 class 里。但仔细看,main 是 static 的。staticmethod 不能直接 callnon-staticmethods。可改
成"System.out.println(“s.doSomething()returns”+s.doSomething());"。同理,staticmethod 不能访
问 non-staticinstantvariable。
范例 009:易被当错的命名
此处,Something 类的文件名叫 OtherThing.java
classSomething{
privatestaticvoidmain(String[]something_to_do){
System.out.println(“Dosomething…”);
}
}
这个好像很明显。
答案:正确。从来没有人说过 Java 的 Class 名字必须和其文件名相同。但 publicclass 的
名字必须和文件名相同。
范例 010:未明确的 x 调用
interfaceA{
intx=0;
4 / 54
}
classB{
intx=1;
}
classCextendsBimplementsA{
publicvoidpX(){
System.out.println(x);
}
publicstaticvoidmain(String[]args){
newC().pX();
}
}
答案:错误。在编译时会发生错误(错误描述不同的 JVM 有不同的信息,意思就是未明
确的 x 调用,两个 x 都匹配(就象在同时 importjava.util 和 java.sql 两个包时直接声明 Date
一样)。对于父类的变量,可以用 super.x 来明确,而接口的属性默认隐含为 publicstaticfinal.
所以可以通过 A.x 来明确。
范例 011:Ball 类的 Play()方法问题
interfacePlayable{
voidplay();
}
interfaceBounceable{
voidplay();
}
interfaceRollableextendsPlayable,Bounceable{
Ballball=newBall(“PingPang”);
}
classBallimplementsRollable{
privateStringname;
publicStringgetName(){
returnname;
}
publicBall(Stringname){
this.name=name;
}
publicvoidplay(){
ball=newBall(“Football”);
System.out.println(ball.getName());
}
}
这个错误不容易发现。
答案:错。“interfaceRollableextendsPlayable,Bounceable"没有问题。interface 可继承多个
interfaces,所以这里没错。问题出在 interfaceRollable 里的"Ballball=newBall(“PingPang”);”。
5 / 54
6 / 54
任何在 interface 里 声 明 的 interfacevariable( 接口变量,也可称成员变量 ) ,默认为
publicstaticfinal 。 也 就 是 说 “Ballball=newBall(“PingPang”);” 实 际 上 是
“publicstaticfinalBallball=newBall(“PingPang”);” 。 在 Ball 类 的 Play() 方法中,
"ball=newBall(“Football”);"改变了 ball 的 reference,而这里的 ball 来自 Rollableinterface,
Rollableinterface 里的 ball 是 publicstaticfinal 的,final 的 object 是不能被改变 reference 的。
因此编译器将在"ball=newBall(“Football”);"这里显示有错。
范例 012:字符串连接误用
错误的写法:

  1. Strings=“”;
  2. for(Personp:persons){
  3. s+=“,”+p.getName();
  4. }
  5. s=s.substring(2);//removefirstcomma
    正确的写法:
  6. StringBuildersb=newStringBuilder(persons.size()*16);//wellestimatedbuffe
    r
  7. for(Personp:persons){
  8. if(sb.length()>0)sb.append(“,”);
  9. sb.append(p.getName);
  10. }
    范例 013:错误的使用 StringBuffer
    错误的写法:
  11. StringBuffersb=newStringBuffer();
  12. sb.append(“Name:”);
  13. sb.append(name+‘\n’);
  14. sb.append(“!”);
  15. Strings=sb.toString();
    问题在第三行,appendchar 比 String 性能要好,另外就是初始化 StringBuffer 没有指定
    size,导致中间 append 时可能重新调整内部数组大小。如果是 JDK1.5 最好用 StringBuilder
    取代 StringBuffer,除非有线程安全的要求。还有一种方式就是可以直接连接字符串。缺点
    就是无法初始化时指定长度。
    正确的写法:
  16. StringBuildersb=newStringBuilder(100);
  17. sb.append(“Name:”);
  18. sb.append(name);
  19. sb.append(“\n!”);
  20. Strings=sb.toString();
    或者这样写:
  21. Strings=“Name:”+name+“\n!”;
    范例 014:测试字符串相等性
    错误的写法:
  22. if(name.compareTo(“John”)==0)…
  23. if(name==“John”)…
  24. if(name.equals(“John”))…
  25. if(“”.equals(name))…
    上面的代码没有错,但是不够好。compareTo 不够简洁,==原义是比较两个对象是否一样。另外比较字符是否为空,最好判断它的长度。
    正确的写法:
  26. if(“John”.equals(name))…
  27. if(name.length()==0)…
  28. if(name.isEmpty())…
    范例 015:数字转换成字符串
    错误的写法:
  29. “”+set.size()
  30. newInteger(set.size()).toString()
    正确的写法:
  31. String.valueOf(set.size())
    范例 016:利用不可变对象(Immutable)
    错误的写法:
  32. zero=newInteger(0);
    7 / 54
  33. returnBoolean.valueOf(“true”);
    正确的写法:
  34. zero=Integer.valueOf(0);
  35. returnBoolean.TRUE;
    范例 017:请使用 XML 解析器
    错误的写法:
  36. intstart=xml.indexOf(“”)+“”.length();
  37. intend=xml.indexOf(“”);
  38. Stringname=xml.substring(start,end);
    正确的写法:
  39. SAXBuilderbuilder=newSAXBuilder(false);
  40. Documentdoc=doc=builder.build(newStringReader(xml));
  41. Stringname=doc.getRootElement().getChild(“name”).getText();
    范例 018:请使用 JDom 组装 XML
    错误的写法:
  42. Stringname=…
  43. Stringattribute=…
  44. Stringxml=“”
  45. +“<nameatt=”“+attribute+”“>”+name+“”
  46. +“”;
    正确的写法:
  47. Elementroot=newElement(“root”);
  48. root.setAttribute(“att”,attribute);
  49. root.setText(name);
  50. Documentdoc=newDocumet();
  51. doc.setRootElement(root);
  52. XmlOutputterout=newXmlOutputter(Format.getPrettyFormat());
  53. Stringxml=out.outputString(root);
    8 / 54
    范例 019:XML 编码陷阱
    错误的写法:
  54. Stringxml=FileUtils.readTextFile(“my.xml”);
    因为 xml 的编码在文件中指定的,而在读文件的时候必须指定编码。另外一个问题不
    能一次就将一个 xml 文件用 String 保存,这样对内存会造成不必要的浪费,正确的做法用
    InputStream 来边读取边处理。为了解决编码的问题,最好使用 XML 解析器来处理。
    范例 020:未指定字符编码
    错误的写法:
  55. Readerr=newFileReader(file);
  56. Writerw=newFileWriter(file);
  57. Readerr=newInputStreamReader(inputStream);
  58. Writerw=newOutputStreamWriter(outputStream);
  59. Strings=newString(byteArray);//byteArrayisabyte[]
  60. byte[]a=string.getBytes();
    这样的代码主要不具有跨平台可移植性。因为不同的平台可能使用的是不同的默认字
    符编码。
    正确的写法:
  61. Readerr=newInputStreamReader(newFileInputStream(file),“ISO-8859-1”);
  62. Writerw=newOutputStreamWriter(newFileOutputStream(file),“ISO-8859-1”);
  63. Readerr=newInputStreamReader(inputStream,“UTF-8”);
  64. Writerw=newOutputStreamWriter(outputStream,“UTF-8”);
  65. Strings=newString(byteArray,“ASCII”);
  66. byte[]a=string.getBytes(“ASCII”);
    范例 021:未对数据流进行缓存
    错误的写法:
  67. InputStreamin=newFileInputStream(file);
  68. intb;
  69. while((b=in.read())!=-1){
  70. }

上面的代码是一个 byte 一个 byte 的读取,导致频繁的本地 JNI 文件系统访问,非常低
效,因为调用本地方法是非常耗时的。最好用 BufferedInputStream 包装一下。曾经做过一个
测试,从/dev/zero 下读取 1MB,大概花了 1s,而用 BufferedInputStream 包装之后只需要 60ms,
性能提高了 94%!这个也适用于 outputstream 操作以及 socket 操作。
正确的写法:

  1. InputStreamin=newBufferedInputStream(newFileInputStream(file));
    范例 022:无限使用 heap 内存
    错误的写法:
  2. byte[]pdf=toPdf(file);
    这里有一个前提,就是文件大小不能讲 JVM 的 heap 撑爆。否则就等着 OOM 吧,尤其是在高并发的服务器端代码。最好的做法是采用 Stream 的方式边读取边存储(本地文件或
    database)。
    正确的写法:
  3. Filepdf=toPdf(file);
    另外,对于服务器端代码来说,为了系统的安全,至少需要对文件的大小进行限制。
    范例 023:不指定超时时间
    错误的代码:
  4. Socketsocket=…
  5. socket.connect(remote);
  6. InputStreamin=socket.getInputStream();
  7. inti=in.read();
    这种情况在工作中已经碰到不止一次了。个人经验一般超时不要超过 20s。这里有一个问题,connect 可以指定超时时间,但是 read 无法指定超时时间。但是可以设置阻塞(block)
    时间。
    正确的写法:
  8. Socketsocket=…
  9. socket.connect(remote,20000);//failafter20s
  10. InputStreamin=socket.getInputStream();
  11. socket.setSoTimeout(15000);
  12. inti=in.read();

另外,文件的读取(FileInputStream,FileChannel,FileDescriptor,File)没法指定超时时间,而且 IO 操作均涉及到本地方法调用,这个更操作了 JVM 的控制范围,在分布式文件系统中,对 IO 的操作内部实际上是网络调用。一般情况下操作 60s 的操作都可以认为已经超时了。
为了解决这些问题,一般采用缓存和异步/消息队列处理。
范例 024:频繁使用计时器
错误代码:

  1. for(…){

  2. longt=System.currentTimeMillis();

  3. longt=System.nanoTime();

  4. Dated=newDate();

  5. Calendarc=newGregorianCalendar();

  6. }
    每次 new 一个 Date 或 Calendar 都会涉及一次本地调用来获取当前时间(尽管这个本地调用相对其他本地方法调用要快)。如果对时间不是特别敏感,这里使用了 clone 方法来新建一个 Date 实例。这样相对直接 new 要高效一些。
    正确的写法:

  7. Dated=newDate();

  8. for(Eentity:entities){

  9. entity.doSomething();

  10. entity.setUpdated((Date)d.clone());

  11. }
    如果循环操作耗时较长(超过几 ms),那么可以采用下面的方法,立即创建一个 Timer,然后定期根据当前时间更新时间戳,在我的系统上比直接 new 一个时间对象快 200 倍:

  12. privatevolatilelongtime;

  13. Timertimer=newTimer(true);

  14. try{

  15. time=System.currentTimeMillis();

  16. timer.scheduleAtFixedRate(newTimerTask(){

  17. publicvoidrun(){

  18. time=System.currentTimeMillis();

  19. }

  20. },0L,10L);//granularity10ms

  21. for(Eentity:entities){

  22. entity.doSomething();

  23. entity.setUpdated(newDate(time));

  24. }

  25. }finally{

  26. timer.cancel();

  27. }
    范例 025:捕获所有的异常
    错误的写法:

  28. Queryq=…

  29. Personp;

  30. try{

  31. p=(Person)q.getSingleResult();

  32. }catch(Exceptione){

  33. p=null;

  34. }
    这是 EJB3 的一个查询操作,可能出现异常的原因是:结果不唯一;没有结果;数据库无法访问,而捕获所有的异常,设置为 null 将掩盖各种异常情况。
    正确的写法:

  35. Queryq=…

  36. Personp;

  37. try{

  38. p=(Person)q.getSingleResult();

  39. }catch(NoResultExceptione){

  40. p=null;

  41. }
    范例 026:忽略所有异常
    错误的写法:

  42. try{

  43. doStuff();

  44. }catch(Exceptione){

  45. log.fatal(“Couldnotdostuff”);

  46. }

  47. doMoreStuff();
    这个代码有两个问题,一个是没有告诉调用者,系统调用出错了.第二个是日志没有出错
    原因,很难跟踪定位问题。
    正确的写法:
    12 / 54

  48. try{

  49. doStuff();

  50. }catch(Exceptione){

  51. thrownewMyRuntimeException(“Couldnotdostuffbecause:”+e.getMessage,e);

  52. }
    范例 027:重复包装 RuntimeException
    错误的写法:

  53. try{

  54. doStuff();

  55. }catch(Exceptione){

  56. thrownewRuntimeException(e);

  57. }
    正确的写法:

  58. try{

  59. doStuff();

  60. }catch(RuntimeExceptione){

  61. throwe;

  62. }catch(Exceptione){

  63. thrownewRuntimeException(e.getMessage(),e);

  64. }

  65. try{

  66. doStuff();

  67. }catch(IOExceptione){

  68. thrownewRuntimeException(e.getMessage(),e);

  69. }catch(NamingExceptione){

  70. thrownewRuntimeException(e.getMessage(),e);

  71. }
    范例 028:不正确的传播异常
    错误的写法:

  72. try{

  73. }catch(ParseExceptione){

  74. thrownewRuntimeException();

  75. thrownewRuntimeException(e.toString());

  76. thrownewRuntimeException(e.getMessage());

  77. thrownewRuntimeException(e);
    13 / 54

  78. }
    主要是没有正确的将内部的错误信息传递给调用者.第一个完全丢掉了内部错误信息,
    第二个错误信息依赖 toString 方法,如果没有包含最终的嵌套错误信息,也会出现丢失,而且可
    读性差.第三个稍微好一些,第四个跟第二个一样。
    正确的写法:

  79. try{

  80. }catch(ParseExceptione){

  81. thrownewRuntimeException(e.getMessage(),e);

  82. }
    范例 029:用日志记录异常
    错误的写法:

  83. try{

  84. }catch(ExceptionAe){

  85. log.error(e.getMessage(),e);

  86. throwe;

  87. }catch(ExceptionBe){

  88. log.error(e.getMessage(),e);

  89. throwe;

  90. }
    一般情况下在日志中记录异常是不必要的,除非调用方没有记录日志。
    范例 030:异常处理不彻底
    错误的写法:

  91. try{

  92. is=newFileInputStream(inFile);

  93. os=newFileOutputStream(outFile);

  94. }finally{

  95. try{

  96. is.close();

  97. os.close();

  98. }catch(IOExceptione){

  99. /wecan’tdoanything/

  100. }

  101. }
    14 / 54
    is 可能 close 失败,导致 os 没有 close
    正确的写法:

  102. try{

  103. is=newFileInputStream(inFile);

  104. os=newFileOutputStream(outFile);

  105. }finally{

  106. try{if(is!=null)is.close();}catch(IOExceptione){/wecan’tdoanything/}

  107. try{if(os!=null)os.close();}catch(IOExceptione){/wecan’tdoanything/}

  108. }
    范例 031:捕获不可能出现的异常
    错误的写法:

  109. try{

  110. …doriskystuff…

  111. }catch(SomeExceptione){

  112. //neverhappens

  113. }

  114. …dosomemore…
    正确的写法:

  115. try{

  116. …doriskystuff…

  117. }catch(SomeExceptione){

  118. //neverhappenshopefully

  119. thrownewIllegalStateException(e.getMessage(),e);//crashearly,passingalli
    nformation

  120. }

  121. …dosomemore…
    范例 032:transient 的误用
    错误的写法:

  122. publicclassAimplementsSerializable{

  123. privateStringsomeState;

  124. privatetransientLoglog=LogFactory.getLog(getClass());

  125. publicvoidf(){

  126. log.debug(“enterf”);


  127. 15 / 54

  128. }

  129. }
    这里的本意是不希望 Log 对象被序列化.不过这里在反序列化时,会因为 log 未初始化,
    导致 f()方法抛空指针,正确的做法是将 log 定义为静态变量或者定位为具备变量。
    正确的写法:

  130. publicclassAimplementsSerializable{

  131. privateStringsomeState;

  132. privatestaticfinalLoglog=LogFactory.getLog(A.class);

  133. publicvoidf(){

  134. log.debug(“enterf”);

  135. }

  136. }

  137. publicclassAimplementsSerializable{

  138. privateStringsomeState;

  139. publicvoidf(){

  140. Loglog=LogFactory.getLog(getClass());

  141. log.debug(“enterf”);

  142. }

  143. }
    范例 033:不必要的初始化
    错误的写法:

  144. publicclassB{

  145. privateintcount=0;

  146. privateStringname=null;

  147. privatebooleanimportant=false;

  148. }
    这里的变量会在初始化时使用默认值:0,null,false,因此上面的写法有些多此一举。
    正确的写法:

  149. publicclassB{

  150. privateintcount;

  151. privateStringname;

  152. privatebooleanimportant;

  153. }
    16 / 54
    17 / 54
    范例 034:最好用静态 final 定义 Log 变量

  154. privatestaticfinalLoglog=LogFactory.getLog(MyClass.class);
    这样做的好处有三:
     可以保证线程安全
     静态或非静态代码都可用
     不会影响对象序列化
    范例 035:选择错误的类加载器
    错误的代码:

  155. Classclazz=Class.forName(name);

  156. Classclazz=getClass().getClassLoader().loadClass(name);
    这里本意是希望用当前类来加载希望的对象,但是这里的 getClass()可能抛出异常,特别
    在一些受管理的环境中,比如应用服务器,web 容器,JavaWebStart 环境中,最好的做法是使用当
    前应用上下文的类加载器来加载。
    正确的写法:

  157. ClassLoadercl=Thread.currentThread().getContextClassLoader();

  158. if(cl==null)cl=MyClass.class.getClassLoader();//fallback

  159. Classclazz=cl.loadClass(name);
    范例 036:反射使用不当
    错误的写法:

  160. ClassbeanClass=…

  161. if(beanClass.newInstance()instanceofTestBean)…
    这里的本意是检查 beanClass 是否是 TestBean 或是其子类,但是创建一个类实例可能没
    那么简单,首先实例化一个对象会带来一定的消耗,另外有可能类没有定义默认构造函数.正
    确的做法是用 Class.isAssignableFrom(Class)方法。
    正确的写法:

  162. ClassbeanClass=…

  163. if(TestBean.class.isAssignableFrom(beanClass))…
    18 / 54
    范例 037:不必要的同步
    错误的写法:

  164. Collectionl=newVector();

  165. for(…){

  166. l.add(object);

  167. }
    Vector 是 ArrayList 同步版本。
    正确的写法:

  168. Collectionl=newArrayList();

  169. for(…){

  170. l.add(object);

  171. }
    范例 038:错误的选择 List 类型
    根据下面的表格数据来进行选择
    ArrayList LinkedList
    add(append) O(1)or~O(log(n))ifgrowing O(1)
    insert(middle) O(n)or~O(n*log(n))ifgrowing O(n)
    remove(middle) O(n)(alwaysperformscompletecopy) O(n)
    iterate O(n) O(n)
    getbyindex O(1) O(n)
    范例 039:HashMapsize 陷阱
    错误的写法:

  172. Mapmap=newHashMap(collection.size());

  173. for(Objecto:collection){

  174. map.put(o.key,o.value);

  175. }
    这里可以参考 guava 的 Maps.newHashMapWithExpectedSize 的实现.用户的本意是希望
    给 HashMap 设置初始值,避免扩容(resize)的开销.但是没有考虑当添加的元素数量达到
    HashMap 容量的 75%时将出现 resize。
    19 / 54
    正确的写法:

  176. Mapmap=newHashMap(1+(int)(collection.size()/0.75));
    范例 040:对 Hashtable,HashMap 和 HashSet 了解不够
    这里主要需要了解 HashMap 和 Hashtable 的内部实现上,它们都使用 Entry 包装来封装
    key/value,Entry 内部除了要保存 Key/Value 的引用,还需要保存 hash 桶中 nextEntry 的应用,因
    此对内存会有不小的开销 , 而 HashSet 内部实现其实就是一个 HashMap. 有时候
    IdentityHashMap 可以作为一个不错的替代方案.它在内存使用上更有效(没有用 Entry 封装,
    内部采用 Object[]).不过需要小心使用.它的实现违背了 Map 接口的定义.有时候也可以用
    ArrayList 来替换 HashSet.
    这一切的根源都是由于 JDK 内部没有提供一套高效的 Map 和 Set 实现。
    范例 041:对 List 的误用
    建议下列场景用 Array 来替代 List:
     list 长度固定,比如一周中的每一天
     对 list 频繁的遍历,比如超过 1w 次
     需要对数字进行包装(主要 JDK 没有提供基本类型的 List)
    比如下面的代码。
    错误的写法:

  177. Listcodes=newArrayList();

  178. codes.add(Integer.valueOf(10));

  179. codes.add(Integer.valueOf(20));

  180. codes.add(Integer.valueOf(30));

  181. codes.add(Integer.valueOf(40));
    正确的写法:

  182. int[]codes={10,20,30,40};
    错误的写法:

  183. //horriblyslowandamemorywasteriflhasafewthousandelements(tryityourself!)

  184. Listl=…;

  185. for(inti=0;i<l.size()-1;i++){

  186. Mergeableone=l.get(i);

  187. Iteratorj=l.iterator(i+1);//memoryallocation!

  188. while(j.hasNext()){

  189. Mergeableother=l.next();

  190. if(one.canMergeWith(other)){

  191. one.merge(other);

  192. other.remove();

  193. }

  194. }

  195. }
    正确的写法:

  196. //quitefastandnomemoryallocation

  197. Mergeable[]l=…;

  198. for(inti=0;i<l.length-1;i++){

  199. Mergeableone=l[i];

  200. for(intj=i+1;j<l.length;j++){

  201. Mergeableother=l[j];

  202. if(one.canMergeWith(other)){

  203. one.merge(other);

  204. l[j]=null;

  205. }

  206. }

  207. }
    实际上 Sun也意识到这一点,因此在 JDK中,Collections.sort()就是将一个 List 拷贝到一个
    数组中然后调用 Arrays.sort 方法来执行排序。
    范例 042:用数组来描述一个结构
    错误用法:

  208. /**

  209. *@returns[1]:Location,[2]:Customer,[3]:Incident

  210. */

  211. Object[]getDetails(intid){…
    这里用数组+文档的方式来描述一个方法的返回值.虽然很简单,但是很容易误用,正确的
    做法应该是定义个类。
    正确的写法:

  212. DetailsgetDetails(intid){…}

  213. privateclassDetails{

  214. publicLocationlocation;

  215. publicCustomercustomer;

  216. publicIncidentincident;
    20 / 54

  217. }
    范例 043:对方法过度限制
    错误用法:

  218. publicvoidnotify(Personp){

  219. sendMail(p.getName(),p.getFirstName(),p.getEmail());

  220. }

  221. classPhoneBook{

  222. Stringlookup(StringemployeeId){

  223. Employeeemp=…

  224. returnemp.getPhone();

  225. }

  226. }
    第一个例子是对方法参数做了过多的限制,第二个例子对方法的返回值做了太多的限
    制。
    正确的写法:

  227. publicvoidnotify(Personp){

  228. sendMail§;

  229. }

  230. classEmployeeDirectory{

  231. Employeelookup(StringemployeeId){

  232. Employeeemp=…

  233. returnemp;

  234. }

  235. }
    范例 044:对 POJO 的 setter 方法画蛇添足
    错误的写法:

  236. privateStringname;

  237. publicvoidsetName(Stringname){

  238. this.name=name.trim();

  239. }

  240. publicvoidStringgetName(){
    21 / 54

  241. returnthis.name;

  242. }
    有时候我们很讨厌字符串首尾出现空格,所以在 setter 方法中进行了 trim 处理,但是这样
    做的结果带来的副作用会使 getter 方法的返回值和 setter 方法不一致,如果只是将 JavaBean
    当做一个数据容器,那么最好不要包含任何业务逻辑.而将业务逻辑放到专门的业务层或者控
    制层中处理。
    正确的做法:

  243. person.setName(textInput.getText().trim());
    范例 045:日历对象(Calendar)误用
    错误的写法:

  244. Calendarcal=newGregorianCalender(TimeZone.getTimeZone(“Europe/Zurich”));

  245. cal.setTime(date);

  246. cal.add(Calendar.HOUR_OF_DAY,8);

  247. date=cal.getTime();
    这里主要是对 date,time,calendar 和 timezone 不了解导致.而在一个时间上增加 8 小时,跟
    timezone 没有任何关系,所以没有必要使用 Calendar,直接用 Date 对象即可,而如果是增加天数
    的话,则需要使用 Calendar,因为采用不同的时令制可能一天的小时数是不同的(比如有些DST
    是 23 或者 25 个小时)
    正确的写法:

  248. date=newDate(date.getTime()+8L3600L1000L);//add8hrs
    范例 046:TimeZone 的误用
    错误的写法:

  249. Calendarcal=newGregorianCalendar();

  250. cal.setTime(date);

  251. cal.set(Calendar.HOUR_OF_DAY,0);

  252. cal.set(Calendar.MINUTE,0);

  253. cal.set(Calendar.SECOND,0);

  254. DatestartOfDay=cal.getTime();
    这里有两个错误,一个是没有没有将毫秒归零,不过最大的错误是没有指定 TimeZone,不
    过一般的桌面应用没有问题,但是如果是服务器端应用则会有一些问题,比如同一时刻在上海
    和伦敦就不一样,因此需要指定的 TimeZone.
    22 / 54
    正确的写法:

  255. Calendarcal=newGregorianCalendar(user.getTimeZone());

  256. cal.setTime(date);

  257. cal.set(Calendar.HOUR_OF_DAY,0);

  258. cal.set(Calendar.MINUTE,0);

  259. cal.set(Calendar.SECOND,0);

  260. cal.set(Calendar.MILLISECOND,0);

  261. DatestartOfDay=cal.getTime();
    范例 047:时区(TimeZone)调整的误用
    错误的写法:

  262. publicstaticDateconvertTz(Datedate,TimeZonetz){

  263. Calendarcal=Calendar.getInstance();

  264. cal.setTimeZone(TimeZone.getTimeZone(“UTC”));

  265. cal.setTime(date);

  266. cal.setTimeZone(tz);

  267. returncal.getTime();

  268. }
    这个方法实际上没有改变时间,输入和输出是一样的.关于时间的问题可以参考这篇文
    章:http://www.odi.ch/prog/design/datetime.php这里主要的问题是Date对象并不包含TimeZone
    信息.它总是使用 UTC(世界统一时间).而调用 Calendar 的 getTime/setTime 方法会自动在当前
    时区和 UTC 之间做转换。
    范例 048:Calendar.getInstance()的误用
    错误的写法:

  269. Calendarc=Calendar.getInstance();

  270. c.set(2009,Calendar.JAN