主题:程序员必知:Java代码常见的十种错误
每一个顺序员在编写代码的进程中都免不了呈现错误或是小的失误, 这些小的错误和失误往往使得顺序员还得返工。 那么, 如何才干尽量防止这些错误的发生呢?笔者总结只要在日常的编写代码中总结出经历, 在这篇文章中, 笔者列出了10个Java编程中罕见的错误, 你可以把这些错误添加到你的代码审查的反省列表中, 这样在经过代码审查后, 你可以确信你的代码中不再存在这类错误了。 一、罕见错误1:屡次拷贝字符串 测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。 不可变对象是不可改变的, 因此不需要拷贝它。 最常用的不可变对象是String。 如果你必需改变一个String对象的内容, 你应该使用StringBuffer。 上面的代码会正常工作: String s = new String ("Text here"); 但是, 这段代码性能差, 而且没有必要这么复杂。 你还可以用以下的方式来重写上面的代码: String temp = "Text here"; String s = new String (temp); 但是这段代码包含额定的String, 并非完全必要。 更好的代码为: String s = "Text here"; 二、罕见错误2:没有克隆(clone)返回的对象 封装(encapsulation)是面向对象编程的重要概念。 不幸的是, Java为不小心打破封装提供了方便Java允许返回私有数据的引用(reference)。 上面的代码提醒了这一点: import java. awt. Dimension; /** *//***Example class. The x and y values should never*be negative. */ public class Example. . . { private Dimension d = new Dimension (0, 0); public Example (). . . { } /** *//*** Set border="1" Height and width. Both border="1" Height and width must be nonnegative * or an exception is thrown. */ public synchronized void setValues (int border="1" Height, int width) throws IllegalArgumentException. . . { if (border="1" Height <0 || width <0) throw new IllegalArgumentException(); d. border="1" height = border="1" Height; d. width = width; } public synchronized Dimension getValues(). . . { // Ooops! Breaks encapsulation return d; } } Example类保证了它所存储的border="1" Height和width值永远非负数, 试图使用setValues()方法来设置负值会触发异常。 不幸的是, 由于getValues()返回d的引用, 而不是d的拷贝, 你可以编写如下的破坏性代码: Example ex = new Example(); Dimension d = ex. getValues(); d. border="1" height = -5; d. width = -10; 如今, Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和border="1" Height值, 那么仅凭测试是不可能检测到这类的错误。 不幸的是, 随着时间的推移, 客户代码可能会改变返回的Dimension对象的值, 这个时候, 跟随错误的本源是件单调且费时的事情, 尤其是在多线程环境中。 更好的方式是让getValues()返回拷贝: public synchronized Dimension getValues(). . . { return new Dimension (d. x, d. y); } 如今, Example对象的内部形态就平安了。 调用者可以根据需要改变它所得到的拷贝的形态, 但是要修改Example对象的内部形态, 必需通过setValues()才可以。 三、罕见错误3:不必要的克隆 我们如今晓得了get方法应该返回内部数据对象的拷贝, 而不是引用。 但是, 事情没有相对: /** *//*** Example class. The value should never * be negative. */ public class Example. . . { private Integer i = new Integer (0); public Example (). . . { } /** *//*** Set x. x must be nonnegative* or an exception will be thrown*/ public synchronized void setValues (int x) throws IllegalArgumentException. . . { if (x <0) throw new IllegalArgumentException(); i = new Integer (x); } public synchronized Integer getValue(). . . { // We can"t clone Integers so we makea copy this way. return new Integer (i. intValue()); } } 这段代码是平安的, 但是就象在错误1#那样, 又作了多余的工作。 Integer对象, 就象String对象那样, 一旦被创立就是不可变的。 因此, 返回内部Integer对象, 而不是它的拷贝, 也是平安的。 方法getValue()应该被写为: public synchronized Integer getValue(). . . { // "i" is immutable, so it is safe to return it instead of a copy. return i; } Java顺序比C++顺序包含更多的不可变对象。 JDK 所提供的若干不可变类包括: ·Boolean ·Byte ·Character ·Class ·Double ·Float ·Integer ·Long ·Short ·String ·大局部的Exception的子类 四、罕见错误4:自编代码来拷贝数组 Java允许你克隆数组, 但是开发者通常会错误地编写如下的代码, 效果在于如下的循环用三行做的事情, 如果采用Object的clone方法用一行就可以完成: public class Example. . . { private int[] copy; /** *//*** Save a copy of "data". "data" cannot be null. */ public void saveCopy (int[] data). . . { copy = new int[data. length]; for (int i = 0; i copy[i] = data[i]; } } 这段代码是正确的, 但却不必要地复杂。 saveCopy()的一个更好的完成是: void saveCopy (int[] data). . . { try. . . { copy = (int[])data. clone(); }catch (CloneNotSupportedException e). . . { // Can"t get here. } } 如果你经常克隆数组, 编写如下的一个工具方法会是个好主意: static int[] cloneArray (int[] data). . . { try. . . { return(int[])data. clone(); }catch(CloneNotSupportedException e). . . { // Can"t get here. } } 这样的话, 我们的saveCopy看起来就更简约了: void saveCopy (int[] data). . . { copy = cloneArray ( data); } 五、罕见错误5:拷贝错误的数据 有时候顺序员晓得必需返回一个拷贝, 但是却不小心拷贝了错误的数据。 由于仅仅做了局部的数据拷贝工作, www.dota8.cn dota omg ai 上面的代码与顺序员的意图有偏差: import java. awt. Dimension; /** *//*** Example class. The border="1" Height and width values should never * be negative. */ public class Example. . . { static final public int TOTAL_VALUES = 10; private Dimension[] d = new Dimension[TOTAL_VALUES]; public Example (). . . { } /** *//*** Set border="1" Height and width. Both border="1" Height and width must be nonnegative * or an exception will be thrown. */ public synchronized void setValues (int index, int border="1" Height, int width) throws IllegalArgumentException. . . { if (border="1" Height <0 || width <0) throw new IllegalArgumentException(); if (d[index] == null) d[index] = new Dimension(); d[index]. border="1" height = border="1" Height; d[index]. width = width; } public synchronized Dimension[] getValues() throws CloneNotSupportedException. . . { return (Dimension[])d. clone(); } } 这儿的效果在于getValues()方法仅仅克隆了数组, 而没有克隆数组中包含的Dimension对象, 因此, 虽然调用者无法改变内部的数组使其元素指向不同的Dimension对象, 但是调用者却可以改变内部的数组元素(也就是Dimension对象)的内容。 方法getValues()的更好版本为: public synchronized Dimension[] getValues() throws CloneNotSupportedException. . . { Dimension[] copy = (Dimension[])d. clone(); for (int i = 0; i // NOTE: Dimension isn"t cloneable. if (d != null) copy[i] = new Dimension (d[i]. border="1" Height, d[i]. width); } return copy; } 在克隆原子类型数据的多维数组的时候, 也会犯类似的错误。 原子类型包括int, float等。 简单的克隆int型的一维数组是正确的, 如下所示: public void store (int[] data) throws CloneNotSupportedException. . . { this. data = (int[])data. clone(); // OK } 拷贝int型的二维数组更复杂些。 Java没有int型的二维数组, 因此一个int型的二维数组实际上是一个这样的一维数组:它的类型为int[]。 简单的克隆int[][]型的数组会犯与上面例子中getValues()方法第一版本同样的错误, 因此应该防止这么做。 上面的例子演示了在克隆int型二维数组时错误的和正确的做法: public void wrongStore (int[][] data) throws CloneNotSupportedException. . . { this. data = (int[][])data. clone(); // Not OK! } public void rightStore (int[][] data). . . { // OK! this. data = (int[][])data. clone(); for (int i = 0; i if (data != null) this. data[i] = (int[])data[i]. clone(); } } TAG: Java JAVA java