在Java编程中,有些知识是无法仅通过语言规范或标准API文档来学习的。在本文中,我将尝试收集一些最常见的习语,尤其是那些难以猜到的习语。(JoshuaBloch的《Effective Java》对这个话题的处理比较透彻,更多的用法可以从这本书中学到。)我把这篇文章的所有代码放在了公共场所。您可以根据自己的喜好复制和修改任何代码片段,无需任何凭据。实施equals()classPerson{Stringname;intbirthYear;byte[]raw;publicbooleanequals(Objectobj){if(!objinstanceofPerson)returnfalse;Personother=(Person)obj;returnname.equals(other.name)&&birthYear==other.birthYear&&Arrays。equals(raw,other.raw);}publicinthashCode(){...}}参数必须是Object类型,不能是外围类。foo.equals(null)必须返回false并且不能抛出NullPointerException。(请注意,任何类的nullinstanceof总是返回false,因此上面的代码将起作用。)原始类型(例如int)的字段使用==进行比较,原始数组的字段使用Arrays.equals()进行比较。覆盖equals()时,请记住相应地覆盖hashCode(),与equals()一致。参考:java.lang.Object.equals(Object)。实现hashCode()classPerson{Stringa;Objectb;bytec;int[]d;publicinthashCode(){returna..}}当对象x和y都具有x.equals(y)==true时,您必须确保x.hashCode()==y.hashCode()。根据逆***,如果x.hashCode()!=y.hashCode(),那么x.equals(y)==false一定为真。当x.equals(y)==false时,您无需保证x.hashCode()!=y.hashCode()。但是,如果你能让它尽可能真实,这将提高哈希表的性能。hashCode()最简单的合法实现是简单地返回0;虽然这个实现是正确的,但是这样会导致HashMap等数据结构运行起来非常慢。参考:java.lang.Object.hashCode()。implementcompareTo()classPersonimplementsComparable{StringfirstName;StringlastName;intbirthdate;//ComparebyfirstName,braktiesbylastName,finallybreaktiesbybirthdatepublicintcompareTo(Personother){if(firstName.compareTo(other.firstName)!=0)returnfirstName.compareTo(other.firstName)}(lastName.compareTo(other.lastName)!=0)returnlastName.compareTo(other.lastName);elseif(生日其他.生日)return1;elsereturn0;}}总是实现通用版本Comparable而不是实现原始类型Comparable。因为这样可以节省代码量,减少不必要的麻烦。只关心返回结果的符号(负数/零/正数),它们的大小并不重要。Comparator.compare()的实现与此类似。参考:java.lang.Comparable。#p#Implementclone()classValuesimplementsCloneable{Stringabc;doublefoo;int[]bars;Datehired;publicValuesclone(){try{Valuesresult=(Values)super.clone();result.bars=result.bars.clone();result.hired=result.hired.clone();returnresult;}catch(CloneNotSupportedExceptione){//ImpossiblethrownewAssertionError(e);}}}使用super.clone()让Object类负责创建新对象。原始类型字段已被正确复制。此外,我们不需要克隆不可变类型,如String和BigInteger。手动深度复制所有非原始字段(对象和数组)。对于实现Cloneable的类,clone()方法不应该抛出CloneNotSupportedException。因此,你需要捕获这个异常并忽略它,或者用一个未经检查的异常包装它。手动实现clone()方法而不是使用Object.clone()方法是可能的,也是合法的。参考:java.lang.Object.clone()、java.lang.Cloneable()。使用StringBuilder或StringBuffer//join(["a","b","c"])->"aandbandc"Stringjoin(Liststrs){StringBuildersb=newStringBuilder();booleanfirst=true;for(Strings:strs){if(first)first=false;elsesb.append("and");sb.append(s);}returnsb.toString();}不要像这样使用重复的字符串连接:s+=item,因为它的时间效率是O(n^2)。使用StringBuilder或StringBuffer时,可以使用append()方法添加文本并使用toString()方法获取整个连接文本。StringBuilder是首选,因为它更快。StringBuffer的所有方法都是同步的,通常不需要同步方法。参考java.lang.StringBuilder、java.lang.StringBuffer。生成一个范围内的随机整数Randomrand=newRandom();//Between1and6,inclusiveintdiceRoll(){returnrand.nextInt(6)+1;}始终使用JavaAPI方法生成一个整数范围内的随机数。不要尝试使用Math.abs(rand.nextInt())%n非确定性用法,因为它的结果会有偏差。另外,它的结果值可能是负数,比如当rand.nextInt()==Integer.MIN_VALUE。参考:java.util.Random.nextInt(int)。使用Iterator.remove()voidfilter(Listlist){for(Iteratoriter=list.iterator();iter.hasNext();){Stringitem=iter.next();if(...)iter.remove();}}remove()方法作用于next()方法返回的最新条目。remove()方法每个条目只能使用一次。参考:java.util.Iterator.remove()。ReturnstringStringreverse(Strings){returnnewStringBuilder(s).reverse().toString();}此方法可能应该添加到Java标准库中。参考:java.lang.StringBuilder.reverse()。启动线程以下三个示例使用不同的方法来完成同一件事。Runnnable的实现方式:voidstartAThread0(){newThread(newMyRunnable()).start();}classMyRunnableimplementsRunnable{publicvoidrun(){...}}Thread的继承方式:voidstartAThread1(){newMyThread().start();}classMyThreadextendsThread{publicvoidrun(){...}}匿名继承Thread的方式:voidstartAThread2(){newThread(){publicvoidrun(){...}}.start();}不要调用run()方法直接。始终调用Thread.start()方法,该方法会创建一个新线程并导致新创建的线程调用run()。参考资料:java.lang.Thread、java.lang.Runnable。#p#使用try-finallyI/O流的例子:voidwriteStuff()throwsIOException{OutputStreamout=newFileOutputStream(...);try{out.write(...);}finally{out.close();}}lockexample:voiddoWithLock(Locklock){lock.acquire();try{...}finally{lock.release();}}如果try之前的语句失败抛出异常,finally语句块将不会执行。但无论如何,不??要担心在这个例子中释放资源。如果try语句块中的语句抛出异常,程序的执行会跳转到finally语句块执行尽可能多的语句,然后跳出这个方法(除非该方法周围还有另一个finally语句块).从输入流中读取字节数据InputStreamin=(...);try{while(true){intb=in.read();if(b==-1)break;(...processb...)}}finally{in.close();}read()方法要么返回从流中读取的下一个字节数(0到255,包括在内),要么在到达流末尾时返回-1。参考:java.io.InputStream.read()。从输入流中读取块数据InputStreamin=(...);try{byte[]buf=newbyte[100];while(true){intn=in.read(buf);if(n==-1)break;(...processbufwithoffset=0andlength=n...)}}finally{in.close();}记住,read()方法不一定填满整个buf,所以你要处理逻辑考虑长度的回报。参考:java.io.InputStream.read(byte[]),java.io.InputStream.read(byte[],int,int)。从文件中读取文本BufferedReaderin=newBufferedReader(newInputStreamReader(newFileInputStream(...),"UTF-8"));try{while(true){Stringline=in.readLine();if(line==null)break;(...processline...)}}finally{in.close();}BufferedReader对象的创建非常冗长。这是因为Java把字节和字符当成两个不同的概念(这点和C语言不同)。除了FileInputStream,您还可以使用任何类型的InputStream,例如套接字。当到达流的末尾时,BufferedReader.readLine()返回null。要一次读取一个字符,请使用Reader.read()方法。您可以使用其他字符编码代替UTF-8,但不要这样做。参考资料:java.io.BufferedReader、java.io.InputStreamReader。将文本写入文件PrintWriterout=newPrintWriter(newOutputStreamWriter(newFileOutputStream(...),"UTF-8"));try{out.print("Hello");out.print(42);out.println("world!");}finally{out.close();}Printwriter对象的创建是乏味的。这是因为Java把字节和字符当成两个不同的概念(这点和C语言不同)。就像System.out一样,可以使用print()和println()来打印各种类型的值。您可以使用其他字符编码代替UTF-8,但不要这样做。参考资料:java.io.PrintWriter、java.io.OutputStreamWriter。防御检查值intfactorial(intn){if(n<0)thrownewIllegalArgumentException("Undefined");elseif(n>=13)thrownewArithmeticException("Resultoverflow");elseif(n==0)return1;elsereturnn*factorial(n-1);}不要以为输入值都是正数,足够小的数等等,这些条件都是显式检查的。一个设计良好的函数应该对所有可能的输入值都能正确执行。确保考虑到所有情况,并且不会产生错误的输出(例如溢出)。预防检测对象intfindIndex(Listlist,Stringtarget){if(list==null||target==null)thrownewNullPointerException();...}不要以为对象参数不会为空(null).明确检查此条件。#p#预防检测数组索引voidfrob(byte[]b,intindex){if(b==null)thrownewNullPointerException();if(index<0||index>=b.length)thrownewIndexOutOfBoundsException();...}不要认为给定的数组索引不会越界。明确地检查它。预防检测数组区间voidfrob(byte[]b,intoff,intlen){if(b==null)thrownewNullPointerException();if(off<0||off>b.length||len<0||b.length-off>>24),(byte)(x>>>16),(byte)(x>>>8),(byte)(x>>>0)};}byte[]unpackLittleEndian(intx){returnnewbyte[]{(byte)(x>>>0),(byte)(x>>>8),(byte)(x>>>16),(byte)(x>>>24)};}总是使用None符号右移运算符(>>>)打包位,请勿使用算术右移运算符(>>)。原文链接:nayuki翻译:ImportNew.com-Jinlin翻译链接:http://www.importnew.com/15605.html