程序员 几个错误
每一个顺序员在编写代码的过程中都免不了出现错误或是小的失误, 这些小的错误和失误往往使得顺序员还得返工。 那么, 如何才能尽量避免这些错误的发作呢?笔者总结只有在日常的编写代码中总结出经验, 在这篇文章中, 笔者列出了几个个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的子类
常见错误四: 自编代码来拷贝数组 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:拷贝错误的数据 有时候顺序员晓得必须返回一个拷贝, 但是却不小心拷贝了错误的数据。 由于仅仅做了部分的数据拷贝工作, 下面的代码与顺序员的意图有偏差:
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();
}
}