经常编写 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:字符串连接误用
错误的写法:
- Strings=“”;
- for(Personp:persons){
- s+=“,”+p.getName();
- }
- s=s.substring(2);//removefirstcomma
正确的写法: - StringBuildersb=newStringBuilder(persons.size()*16);//wellestimatedbuffe
r - for(Personp:persons){
- if(sb.length()>0)sb.append(“,”);
- sb.append(p.getName);
- }
范例 013:错误的使用 StringBuffer
错误的写法: - StringBuffersb=newStringBuffer();
- sb.append(“Name:”);
- sb.append(name+‘\n’);
- sb.append(“!”);
- …
- Strings=sb.toString();
问题在第三行,appendchar 比 String 性能要好,另外就是初始化 StringBuffer 没有指定
size,导致中间 append 时可能重新调整内部数组大小。如果是 JDK1.5 最好用 StringBuilder
取代 StringBuffer,除非有线程安全的要求。还有一种方式就是可以直接连接字符串。缺点
就是无法初始化时指定长度。
正确的写法: - StringBuildersb=newStringBuilder(100);
- sb.append(“Name:”);
- sb.append(name);
- sb.append(“\n!”);
- Strings=sb.toString();
或者这样写: - Strings=“Name:”+name+“\n!”;
范例 014:测试字符串相等性
错误的写法: - if(name.compareTo(“John”)==0)…
- if(name==“John”)…
- if(name.equals(“John”))…
- if(“”.equals(name))…
上面的代码没有错,但是不够好。compareTo 不够简洁,==原义是比较两个对象是否一样。另外比较字符是否为空,最好判断它的长度。
正确的写法: - if(“John”.equals(name))…
- if(name.length()==0)…
- if(name.isEmpty())…
范例 015:数字转换成字符串
错误的写法: - “”+set.size()
- newInteger(set.size()).toString()
正确的写法: - String.valueOf(set.size())
范例 016:利用不可变对象(Immutable)
错误的写法: - zero=newInteger(0);
7 / 54 - returnBoolean.valueOf(“true”);
正确的写法: - zero=Integer.valueOf(0);
- returnBoolean.TRUE;
范例 017:请使用 XML 解析器
错误的写法: - intstart=xml.indexOf(“”)+“”.length();
- intend=xml.indexOf(“”);
- Stringname=xml.substring(start,end);
正确的写法: - SAXBuilderbuilder=newSAXBuilder(false);
- Documentdoc=doc=builder.build(newStringReader(xml));
- Stringname=doc.getRootElement().getChild(“name”).getText();
范例 018:请使用 JDom 组装 XML
错误的写法: - Stringname=…
- Stringattribute=…
- Stringxml=“”
- +“<nameatt=”“+attribute+”“>”+name+“”
- +“”;
正确的写法: - Elementroot=newElement(“root”);
- root.setAttribute(“att”,attribute);
- root.setText(name);
- Documentdoc=newDocumet();
- doc.setRootElement(root);
- XmlOutputterout=newXmlOutputter(Format.getPrettyFormat());
- Stringxml=out.outputString(root);
8 / 54
范例 019:XML 编码陷阱
错误的写法: - Stringxml=FileUtils.readTextFile(“my.xml”);
因为 xml 的编码在文件中指定的,而在读文件的时候必须指定编码。另外一个问题不
能一次就将一个 xml 文件用 String 保存,这样对内存会造成不必要的浪费,正确的做法用
InputStream 来边读取边处理。为了解决编码的问题,最好使用 XML 解析器来处理。
范例 020:未指定字符编码
错误的写法: - Readerr=newFileReader(file);
- Writerw=newFileWriter(file);
- Readerr=newInputStreamReader(inputStream);
- Writerw=newOutputStreamWriter(outputStream);
- Strings=newString(byteArray);//byteArrayisabyte[]
- byte[]a=string.getBytes();
这样的代码主要不具有跨平台可移植性。因为不同的平台可能使用的是不同的默认字
符编码。
正确的写法: - Readerr=newInputStreamReader(newFileInputStream(file),“ISO-8859-1”);
- Writerw=newOutputStreamWriter(newFileOutputStream(file),“ISO-8859-1”);
- Readerr=newInputStreamReader(inputStream,“UTF-8”);
- Writerw=newOutputStreamWriter(outputStream,“UTF-8”);
- Strings=newString(byteArray,“ASCII”);
- byte[]a=string.getBytes(“ASCII”);
范例 021:未对数据流进行缓存
错误的写法: - InputStreamin=newFileInputStream(file);
- intb;
- while((b=in.read())!=-1){
- …
- }
上面的代码是一个 byte 一个 byte 的读取,导致频繁的本地 JNI 文件系统访问,非常低
效,因为调用本地方法是非常耗时的。最好用 BufferedInputStream 包装一下。曾经做过一个
测试,从/dev/zero 下读取 1MB,大概花了 1s,而用 BufferedInputStream 包装之后只需要 60ms,
性能提高了 94%!这个也适用于 outputstream 操作以及 socket 操作。
正确的写法:
- InputStreamin=newBufferedInputStream(newFileInputStream(file));
范例 022:无限使用 heap 内存
错误的写法: - byte[]pdf=toPdf(file);
这里有一个前提,就是文件大小不能讲 JVM 的 heap 撑爆。否则就等着 OOM 吧,尤其是在高并发的服务器端代码。最好的做法是采用 Stream 的方式边读取边存储(本地文件或
database)。
正确的写法: - Filepdf=toPdf(file);
另外,对于服务器端代码来说,为了系统的安全,至少需要对文件的大小进行限制。
范例 023:不指定超时时间
错误的代码: - Socketsocket=…
- socket.connect(remote);
- InputStreamin=socket.getInputStream();
- inti=in.read();
这种情况在工作中已经碰到不止一次了。个人经验一般超时不要超过 20s。这里有一个问题,connect 可以指定超时时间,但是 read 无法指定超时时间。但是可以设置阻塞(block)
时间。
正确的写法: - Socketsocket=…
- socket.connect(remote,20000);//failafter20s
- InputStreamin=socket.getInputStream();
- socket.setSoTimeout(15000);
- inti=in.read();
另外,文件的读取(FileInputStream,FileChannel,FileDescriptor,File)没法指定超时时间,而且 IO 操作均涉及到本地方法调用,这个更操作了 JVM 的控制范围,在分布式文件系统中,对 IO 的操作内部实际上是网络调用。一般情况下操作 60s 的操作都可以认为已经超时了。
为了解决这些问题,一般采用缓存和异步/消息队列处理。
范例 024:频繁使用计时器
错误代码:
-
for(…){
-
longt=System.currentTimeMillis();
-
longt=System.nanoTime();
-
Dated=newDate();
-
Calendarc=newGregorianCalendar();
-
}
每次 new 一个 Date 或 Calendar 都会涉及一次本地调用来获取当前时间(尽管这个本地调用相对其他本地方法调用要快)。如果对时间不是特别敏感,这里使用了 clone 方法来新建一个 Date 实例。这样相对直接 new 要高效一些。
正确的写法: -
Dated=newDate();
-
for(Eentity:entities){
-
entity.doSomething();
-
entity.setUpdated((Date)d.clone());
-
}
如果循环操作耗时较长(超过几 ms),那么可以采用下面的方法,立即创建一个 Timer,然后定期根据当前时间更新时间戳,在我的系统上比直接 new 一个时间对象快 200 倍: -
privatevolatilelongtime;
-
Timertimer=newTimer(true);
-
try{
-
time=System.currentTimeMillis();
-
timer.scheduleAtFixedRate(newTimerTask(){
-
publicvoidrun(){
-
time=System.currentTimeMillis();
-
}
-
},0L,10L);//granularity10ms
-
for(Eentity:entities){
-
entity.doSomething();
-
entity.setUpdated(newDate(time));
-
}
-
}finally{
-
timer.cancel();
-
}
范例 025:捕获所有的异常
错误的写法: -
Queryq=…
-
Personp;
-
try{
-
p=(Person)q.getSingleResult();
-
}catch(Exceptione){
-
p=null;
-
}
这是 EJB3 的一个查询操作,可能出现异常的原因是:结果不唯一;没有结果;数据库无法访问,而捕获所有的异常,设置为 null 将掩盖各种异常情况。
正确的写法: -
Queryq=…
-
Personp;
-
try{
-
p=(Person)q.getSingleResult();
-
}catch(NoResultExceptione){
-
p=null;
-
}
范例 026:忽略所有异常
错误的写法: -
try{
-
doStuff();
-
}catch(Exceptione){
-
log.fatal(“Couldnotdostuff”);
-
}
-
doMoreStuff();
这个代码有两个问题,一个是没有告诉调用者,系统调用出错了.第二个是日志没有出错
原因,很难跟踪定位问题。
正确的写法:
12 / 54 -
try{
-
doStuff();
-
}catch(Exceptione){
-
thrownewMyRuntimeException(“Couldnotdostuffbecause:”+e.getMessage,e);
-
}
范例 027:重复包装 RuntimeException
错误的写法: -
try{
-
doStuff();
-
}catch(Exceptione){
-
thrownewRuntimeException(e);
-
}
正确的写法: -
try{
-
doStuff();
-
}catch(RuntimeExceptione){
-
throwe;
-
}catch(Exceptione){
-
thrownewRuntimeException(e.getMessage(),e);
-
}
-
try{
-
doStuff();
-
}catch(IOExceptione){
-
thrownewRuntimeException(e.getMessage(),e);
-
}catch(NamingExceptione){
-
thrownewRuntimeException(e.getMessage(),e);
-
}
范例 028:不正确的传播异常
错误的写法: -
try{
-
}catch(ParseExceptione){
-
thrownewRuntimeException();
-
thrownewRuntimeException(e.toString());
-
thrownewRuntimeException(e.getMessage());
-
thrownewRuntimeException(e);
13 / 54 -
}
主要是没有正确的将内部的错误信息传递给调用者.第一个完全丢掉了内部错误信息,
第二个错误信息依赖 toString 方法,如果没有包含最终的嵌套错误信息,也会出现丢失,而且可
读性差.第三个稍微好一些,第四个跟第二个一样。
正确的写法: -
try{
-
}catch(ParseExceptione){
-
thrownewRuntimeException(e.getMessage(),e);
-
}
范例 029:用日志记录异常
错误的写法: -
try{
-
…
-
}catch(ExceptionAe){
-
log.error(e.getMessage(),e);
-
throwe;
-
}catch(ExceptionBe){
-
log.error(e.getMessage(),e);
-
throwe;
-
}
一般情况下在日志中记录异常是不必要的,除非调用方没有记录日志。
范例 030:异常处理不彻底
错误的写法: -
try{
-
is=newFileInputStream(inFile);
-
os=newFileOutputStream(outFile);
-
}finally{
-
try{
-
is.close();
-
os.close();
-
}catch(IOExceptione){
-
/wecan’tdoanything/
-
}
-
}
14 / 54
is 可能 close 失败,导致 os 没有 close
正确的写法: -
try{
-
is=newFileInputStream(inFile);
-
os=newFileOutputStream(outFile);
-
}finally{
-
try{if(is!=null)is.close();}catch(IOExceptione){/wecan’tdoanything/}
-
try{if(os!=null)os.close();}catch(IOExceptione){/wecan’tdoanything/}
-
}
范例 031:捕获不可能出现的异常
错误的写法: -
try{
-
…doriskystuff…
-
}catch(SomeExceptione){
-
//neverhappens
-
}
-
…dosomemore…
正确的写法: -
try{
-
…doriskystuff…
-
}catch(SomeExceptione){
-
//neverhappenshopefully
-
thrownewIllegalStateException(e.getMessage(),e);//crashearly,passingalli
nformation -
}
-
…dosomemore…
范例 032:transient 的误用
错误的写法: -
publicclassAimplementsSerializable{
-
privateStringsomeState;
-
privatetransientLoglog=LogFactory.getLog(getClass());
-
publicvoidf(){
-
log.debug(“enterf”);
-
…
15 / 54 -
}
-
}
这里的本意是不希望 Log 对象被序列化.不过这里在反序列化时,会因为 log 未初始化,
导致 f()方法抛空指针,正确的做法是将 log 定义为静态变量或者定位为具备变量。
正确的写法: -
publicclassAimplementsSerializable{
-
privateStringsomeState;
-
privatestaticfinalLoglog=LogFactory.getLog(A.class);
-
publicvoidf(){
-
log.debug(“enterf”);
-
…
-
}
-
}
-
publicclassAimplementsSerializable{
-
privateStringsomeState;
-
publicvoidf(){
-
Loglog=LogFactory.getLog(getClass());
-
log.debug(“enterf”);
-
…
-
}
-
}
范例 033:不必要的初始化
错误的写法: -
publicclassB{
-
privateintcount=0;
-
privateStringname=null;
-
privatebooleanimportant=false;
-
}
这里的变量会在初始化时使用默认值:0,null,false,因此上面的写法有些多此一举。
正确的写法: -
publicclassB{
-
privateintcount;
-
privateStringname;
-
privatebooleanimportant;
-
}
16 / 54
17 / 54
范例 034:最好用静态 final 定义 Log 变量 -
privatestaticfinalLoglog=LogFactory.getLog(MyClass.class);
这样做的好处有三:
可以保证线程安全
静态或非静态代码都可用
不会影响对象序列化
范例 035:选择错误的类加载器
错误的代码: -
Classclazz=Class.forName(name);
-
Classclazz=getClass().getClassLoader().loadClass(name);
这里本意是希望用当前类来加载希望的对象,但是这里的 getClass()可能抛出异常,特别
在一些受管理的环境中,比如应用服务器,web 容器,JavaWebStart 环境中,最好的做法是使用当
前应用上下文的类加载器来加载。
正确的写法: -
ClassLoadercl=Thread.currentThread().getContextClassLoader();
-
if(cl==null)cl=MyClass.class.getClassLoader();//fallback
-
Classclazz=cl.loadClass(name);
范例 036:反射使用不当
错误的写法: -
ClassbeanClass=…
-
if(beanClass.newInstance()instanceofTestBean)…
这里的本意是检查 beanClass 是否是 TestBean 或是其子类,但是创建一个类实例可能没
那么简单,首先实例化一个对象会带来一定的消耗,另外有可能类没有定义默认构造函数.正
确的做法是用 Class.isAssignableFrom(Class)方法。
正确的写法: -
ClassbeanClass=…
-
if(TestBean.class.isAssignableFrom(beanClass))…
18 / 54
范例 037:不必要的同步
错误的写法: -
Collectionl=newVector();
-
for(…){
-
l.add(object);
-
}
Vector 是 ArrayList 同步版本。
正确的写法: -
Collectionl=newArrayList();
-
for(…){
-
l.add(object);
-
}
范例 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 陷阱
错误的写法: -
Mapmap=newHashMap(collection.size());
-
for(Objecto:collection){
-
map.put(o.key,o.value);
-
}
这里可以参考 guava 的 Maps.newHashMapWithExpectedSize 的实现.用户的本意是希望
给 HashMap 设置初始值,避免扩容(resize)的开销.但是没有考虑当添加的元素数量达到
HashMap 容量的 75%时将出现 resize。
19 / 54
正确的写法: -
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)
比如下面的代码。
错误的写法: -
Listcodes=newArrayList();
-
codes.add(Integer.valueOf(10));
-
codes.add(Integer.valueOf(20));
-
codes.add(Integer.valueOf(30));
-
codes.add(Integer.valueOf(40));
正确的写法: -
int[]codes={10,20,30,40};
错误的写法: -
//horriblyslowandamemorywasteriflhasafewthousandelements(tryityourself!)
-
Listl=…;
-
for(inti=0;i<l.size()-1;i++){
-
Mergeableone=l.get(i);
-
Iteratorj=l.iterator(i+1);//memoryallocation!
-
while(j.hasNext()){
-
Mergeableother=l.next();
-
if(one.canMergeWith(other)){
-
one.merge(other);
-
other.remove();
-
}
-
}
-
}
正确的写法: -
//quitefastandnomemoryallocation
-
Mergeable[]l=…;
-
for(inti=0;i<l.length-1;i++){
-
Mergeableone=l[i];
-
for(intj=i+1;j<l.length;j++){
-
Mergeableother=l[j];
-
if(one.canMergeWith(other)){
-
one.merge(other);
-
l[j]=null;
-
}
-
}
-
}
实际上 Sun也意识到这一点,因此在 JDK中,Collections.sort()就是将一个 List 拷贝到一个
数组中然后调用 Arrays.sort 方法来执行排序。
范例 042:用数组来描述一个结构
错误用法: -
/**
-
*@returns[1]:Location,[2]:Customer,[3]:Incident
-
*/
-
Object[]getDetails(intid){…
这里用数组+文档的方式来描述一个方法的返回值.虽然很简单,但是很容易误用,正确的
做法应该是定义个类。
正确的写法: -
DetailsgetDetails(intid){…}
-
privateclassDetails{
-
publicLocationlocation;
-
publicCustomercustomer;
-
publicIncidentincident;
20 / 54 -
}
范例 043:对方法过度限制
错误用法: -
publicvoidnotify(Personp){
-
…
-
sendMail(p.getName(),p.getFirstName(),p.getEmail());
-
…
-
}
-
classPhoneBook{
-
Stringlookup(StringemployeeId){
-
Employeeemp=…
-
returnemp.getPhone();
-
}
-
}
第一个例子是对方法参数做了过多的限制,第二个例子对方法的返回值做了太多的限
制。
正确的写法: -
publicvoidnotify(Personp){
-
…
-
sendMail§;
-
…
-
}
-
classEmployeeDirectory{
-
Employeelookup(StringemployeeId){
-
Employeeemp=…
-
returnemp;
-
}
-
}
范例 044:对 POJO 的 setter 方法画蛇添足
错误的写法: -
privateStringname;
-
publicvoidsetName(Stringname){
-
this.name=name.trim();
-
}
-
publicvoidStringgetName(){
21 / 54 -
returnthis.name;
-
}
有时候我们很讨厌字符串首尾出现空格,所以在 setter 方法中进行了 trim 处理,但是这样
做的结果带来的副作用会使 getter 方法的返回值和 setter 方法不一致,如果只是将 JavaBean
当做一个数据容器,那么最好不要包含任何业务逻辑.而将业务逻辑放到专门的业务层或者控
制层中处理。
正确的做法: -
person.setName(textInput.getText().trim());
范例 045:日历对象(Calendar)误用
错误的写法: -
Calendarcal=newGregorianCalender(TimeZone.getTimeZone(“Europe/Zurich”));
-
cal.setTime(date);
-
cal.add(Calendar.HOUR_OF_DAY,8);
-
date=cal.getTime();
这里主要是对 date,time,calendar 和 timezone 不了解导致.而在一个时间上增加 8 小时,跟
timezone 没有任何关系,所以没有必要使用 Calendar,直接用 Date 对象即可,而如果是增加天数
的话,则需要使用 Calendar,因为采用不同的时令制可能一天的小时数是不同的(比如有些DST
是 23 或者 25 个小时)
正确的写法: -
date=newDate(date.getTime()+8L3600L1000L);//add8hrs
范例 046:TimeZone 的误用
错误的写法: -
Calendarcal=newGregorianCalendar();
-
cal.setTime(date);
-
cal.set(Calendar.HOUR_OF_DAY,0);
-
cal.set(Calendar.MINUTE,0);
-
cal.set(Calendar.SECOND,0);
-
DatestartOfDay=cal.getTime();
这里有两个错误,一个是没有没有将毫秒归零,不过最大的错误是没有指定 TimeZone,不
过一般的桌面应用没有问题,但是如果是服务器端应用则会有一些问题,比如同一时刻在上海
和伦敦就不一样,因此需要指定的 TimeZone.
22 / 54
正确的写法: -
Calendarcal=newGregorianCalendar(user.getTimeZone());
-
cal.setTime(date);
-
cal.set(Calendar.HOUR_OF_DAY,0);
-
cal.set(Calendar.MINUTE,0);
-
cal.set(Calendar.SECOND,0);
-
cal.set(Calendar.MILLISECOND,0);
-
DatestartOfDay=cal.getTime();
范例 047:时区(TimeZone)调整的误用
错误的写法: -
publicstaticDateconvertTz(Datedate,TimeZonetz){
-
Calendarcal=Calendar.getInstance();
-
cal.setTimeZone(TimeZone.getTimeZone(“UTC”));
-
cal.setTime(date);
-
cal.setTimeZone(tz);
-
returncal.getTime();
-
}
这个方法实际上没有改变时间,输入和输出是一样的.关于时间的问题可以参考这篇文
章:http://www.odi.ch/prog/design/datetime.php这里主要的问题是Date对象并不包含TimeZone
信息.它总是使用 UTC(世界统一时间).而调用 Calendar 的 getTime/setTime 方法会自动在当前
时区和 UTC 之间做转换。
范例 048:Calendar.getInstance()的误用
错误的写法: -
Calendarc=Calendar.getInstance();
-
c.set(2009,Calendar.JAN