欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[转载]说地道的Java语言

程序员文章站 2022-06-05 21:24:03
...

说地道的Java语言(译)

总结:

命名规范
    
不要使用缩写

基于驼峰字的非缩写名称要清晰得多,
代码读得比写得多,Java语言就为阅读而被改进的。C语言程序员具有一种几乎无可抗拒的诱惑力去弄乱代码;Java程序员则不会。Java语言会把易读性放在优先于简洁性的位置。

    有一些缩写十分的通用,你使用它而无需感到愧疚:
    针对最大化的max
    针对最小化的min
    针对InputStream的in
    针对OutStream的out
    在catch语句块中(但不是在所有地方),针对一个异常的e或ex。
    针对数字的num,但只能当用于前缀时,如numTokens或numHits。
    针对用于局部的临时变更的tmp--例如,当交换两个值时。

除了上述缩写和其它的一些可能之外,你应该完整地拼写出名称中所有的单词。

变量的声明,初始化和(重复)使用

    在Java语言(以及最新版的C语言)中,变量可以声明在(或靠近)它第一次使用的地方。当你编写Java代码时,就这么做。这能使你的代码安全,更少地出现Bug,并易于阅读。
    与此相关的,Java代码常常在每个变量声明的时候就进行初始化。有时候,C程序员编写的代码却像这样:

int i;
= 7;


Java程序员几乎不会这样写代码,尽管这些代码在语法上是正确的。Java程序员会像下面这样编写代码:

int i = 7;


    这就帮助避免Bug,这样的Bug会导致无意地使用未被初始化的变量。一般地,唯一的例外是,当一个变量要被包含在try-catch/finally块中时。最常出现的情况就是,当代码要在finally语句块中关闭输入流和输出流时,

然而,这几乎是这一例外唯一能发生的时刻。
    终了,该风格的最后一个连锁效应就是Java程序员常常每行只定义一个变量。例如,他们像下面那样初始化三个变量:

int i = 3;
int j = 8;
int k = 9;


他们不会编写这样的代码:

int i=3, j=8, k=9;


这条语句在语法上是正确的,但在任何时候Java程序员都不会这么做,除非出现一种特别的情况,我下面将会涉及到。

因此,通常的Java风格会更简洁些,它只需三行代码,因为它将声明与初始化结合在了一起。

将变量置于循环内

    许多程序员,包括许多经验丰富的Java程序员,也会止步与此。然而,还有一点儿有用的技术能将所有的变量都转移到循环中。你可在for循环的初始化语句中声明超过一个变量,只需通过逗号进行分隔,如清单9所示:
清单9 所有的变量都置入循环内

for (int i = 1, high = 1, low = 1; i < 20; i++) {
  System.out.println(high);
  
int tmp = high;
  high 
= high+ low;
  low 
= tmp;
}


现在才是把仅仅语法上通顺的代码转化成真正的专家级代码。这种紧紧地束缚局部变量作用域的能力就是你为什么看到Java语言代码中的for循环比C语言代码多得多而while循环少得多的一个重要原因。

不要重用变量
由上述可得出的推论就是Java程序员很少为不同的值和对象重用局部变量。例如,清单10为一些按钮设置与之关联的侦听器:
清单10 重用局部变量

Button b = new Button("Play");
b.addActionListener(
new PlayAction());
= new Button("Pause");
b.addActionListener(
new PauseAction());
= new Button("Rewind");
b.addActionListener(
new RewindAction());
= new Button("FastForward");
b.addActionListener(
new FastForwardAction());
= new Button("Stop");
b.addActionListener(
new StopAction());


有经验的Java程序员会使用5个不同的局部变量来重写这段代码,如清单11所示:
清单11 未被重用的变量

Button play = new Button("Play");
play.addActionListener(
new PlayAction());
Button pause 
= new Button("Pause");
pause.addActionListener(
new PauseAction());
Button rewind 
= new Button("Rewind");
rewind.addActionListener(
new RewindAction());
Button fastForward 
= new Button("FastForward");
fastForward.addActionListener(
new FastForwardAction());
Button stop 
= new Button("Stop");
stop.addActionListener(
new StopAction());


为多个逻辑上不同的值或对象重用一个局部变量可能产生Bug。实质上,局部变量(尽管它们并不总是指向对象)在对内存和时间都很敏感的环境中都是适用的。只要你需要,不要惧怕使用众多不同的局部变量。

最好使用基本数据类型
Java语言有8种基本数据类型,但只使用了其中的6种。在Java代码中,float远不如在C代码中用得多。在Java代码中你几乎看不到float 型变量或常量;而double型的则很多。float变量仅仅被用于处理多维浮点型数组,这能在存储空间意义重大的环境中限制数据的精度。否则,使每个变量都为double型。
    比float型还不常见的就是short型。我很少在Java代码中看到short型变量。曾经唯一出现过的情况--我要警告你,这是一种极端罕见的情况 --就是当要读取的外部定义的数据格式中包含16位符号整数类型时。在这种情况下,大部分程序员都会把这些数据当作int型数据去读取。

控制私有作用域
你见过像清单2中示例那样的equals方法吗?
清单12 由C++程序员编写的一个eqauls()方法

public class Foo {

  
private double x;

  
public double getX() {
    
return this.x;
  }

  
public boolean equals(Object o) {
    
if (o instanceof Foo) {
      Foo f 
= (Foo) o;
      
return this.x == f.getX();
    }
    
return false;
  }

}


就技术上而言,该方法是正确的,但我能向你保证这个类是由一位还没改造好的C++程序员写成的。对私有域x的应用并在同一方法甚至同一行中使用公有的 getter方法getX()泄露了这一点。在C++中,这样做是必须的,因为私有性是限定在对象而不是类中的。即,在C++中,同一个类的对象看不到其它对象的私有成员变量,它们必须使用访问器方法。在Java语言中,私有性是限定在类而不是对象中,类型Foo的两个对象中的一个能直接访问到另一个的私有域。
    有些细微的--但往往是不相关的--思考会建议你更应直接访问字段而不是使用访问器方法,或者在Java代码中使用相反的方式。访问字段可能稍快些,但很少见。有时候,通过访问器进行访问相比于直接访问字段可以提供一点儿不同的值,特别是当使用子类时。但是,在Java语言中,没有任何理由在同一个类的同一行中既使用字段访问又使用访问器访问。

标点和语法风格
此处有一些不同于C语言的Java语言风格,其中一些例子应用了特定的Java语言特性。

在类型处放置数组的括号
Java语言可以如C语言那样声明数组:

int k[];
double temperature[];
String names[];


然而,Java语言也提供了另一种语法,将数组括号置于类型而不是变量之后:

int[] k;
double[] temperatures;
String[] names;


大部分Java程序员已经采用了第二种风格。我们就可以说,k是int的数组类型,temperatures是double的数组类型,names是String类型的数组。
与其它局部变量一样,Java程序员也倾向于在数组的声明处对其进行初始化:

int[] k = new int[10];
double[] temperatures = new double[75];
String[] names 
= new String[32];


Use s == null, not null == s

谨慎的C程序员已经学会了将常量放置在比较符的左边。例如:

if (7 == x) doSomething();


在此处,其目的在于避免无意地使用单等于号的赋值操作符,而不是双等于号的比较操作符。

if (7 = x) doSomething();


将常量置于左边会产生一个编译时错误。这项技术是C语言提倡的编程实践。它能帮助防止实际中的Bug,因为将常量置于右边将总是会返回true。
但不同于C语言,Java语言将int与boolean类型分隔开了。赋值操作符返回一个int值,然而比较操作符返回boolean值。结果,if (x = 7)已是一个编译时错误,所以没有任何理由在比较语句中使用不自然的格式if (7 == x),熟练的Java程序员就不会这么做。

连接字符串而不要格式化它们
多年来,Java语言一直没有printf函数。最后是在Java 5中加上了这个方法,但它不太常用。特别地,格式化字符串是针对少数情况的特定领域语言,即当你想将数字格式化成特定的宽度,或在小数点之后有一定数量的空格。然而,C程序员会倾向于在他们的Java代码中过度使用printf方法。一般而言,不要把它用作简单字符串连接的替代器。例如:

System.out.println("There were " + numErrors + " errors reported.");


这强于:

System.out.printf("There were %d errors reported."n", numErrors);


使用字符串连接的版本易于阅读,特别是对于简单情况,并且潜在的Bug会更少,因为不存在格式化字符串中的占位符与参数变量的数量或类型不匹配的风险。

后递增强于前递增
在有些地方,i++与++i之间的区别是有特殊意义的。Java程序员对这样的地方有一个特别的名称,他们称其为"Bug"。
    你编写的代码决不应依赖于前递增与后递增的区别(对于C语言,也是如此)。很难遵循这一规则,也很容易出错。如果你发现自己编写的代码在应用前递增与后递增的区别时产生了错误,你就要重构代码,把这些代码分割成不同的语句以便不再出现这些错误。
    在前递增与后递增的区别无关紧要的地方--例如,for循环中的递增量--相较于先递增,Java程序员更喜欢后递增,大约为4比1。i++要比++i普遍多了。我不能评判其中的原因,但实际情况即如此。如果你写了++i,其他人在读你的代码时就会浪费时间去想为什么你会这样写。所以,你应该总是使用后递增,除非你有一个使用前递增的特殊理由(而你就不应该有使用前递增的理由)。

错误处理
错误处理是Java编程中最困惑的问题之一,也是把程序设计语言的大师级设计者同泛泛之辈区分开的因素之一。实际上,错误处理本身就是一件程序作品的基础。简言之,恰当地使用异常,不要返回错误的代码
    非地道的Java程序员范的第一个错误就是对于程序错误是返回一个值,而不是抛出一个异常。事实上,回溯到最初的Java 1.0时代,在Sun的所有程序员完全熟悉这种新语言之前,你甚至可以在Java语言自己的一些API中看到这种情况。例如,想想 java.io.File中的delete()方法。

public boolean delete()


如果文件或目录被成功删除了,该方法返回true;否则,它返回false。该方法应该做的是,当成功删除时什么都不返回,如果文件因故不能被删除时抛出一个异常:

public void delete() throws IOException


当方法返回程序错误的值时,对该方法的每一个调用都会围绕着对该错误的处理代码。这就使得在通常情况下,当没有任何问题且一切运行良好时,难以遵循和理解该方法的正常执行流程。相反,当由异常来指定错误条件时,则可不使用这种方法,而是将错误处理程序放到文件后面的一个独立代码块中。如果有更合适的地方来处理该问题,甚至可以把它放到其它类和方法中。
    这也带给我在错误处理方面的第二个反模式。来自于C或C++背景的程序员有时候会尝试着在尽可能靠近异常抛出的地方去处理异常。极端的做法,它会过早的处理异常
这些代码难以阅读,甚至比if (errorCondition)测试更让人费解,而异常处理就是被设计来替代这种测试的。流畅的Java代码将错误处理从失败发生的点转移开。它不会把错误处理代码与正常的执行流程混在一起

偶尔,你可能需要嵌套try-catch语句块以将会产生相同异常的不同失败模式分隔开,但这种情况并不常见。一般的经验是,如果在一个方法内有多个有价值的try-catch块,那么该方法就是太大了,无论如何都应该把它恰当地分解成更小的方法。

    最后,来自于其它语言的新接触Java编程的程序员常范地错误就是他们一定要在受检的异常抛出的地方捕获它们。通常,抛出异常的方法不应该捕获该异常。例如,考虑一个复制I/O流的方法,会catch IOException但是不错特殊处理,该方法没有足够的信息来正确地处理可能发生的IOException。它不知道是谁调用了它自己,不知道失败发生的结果。该方法唯一能做的合理的事情就是将IOException抛给它的调用者(在方法上声明可能抛出的异常)。

这段代码更短,更简单,也更聪明,它将错误信息传递给了最适合处理该异常的代码。

这真的是问题吗?
这些都不是严重的问题。其中一些是出于便利的原因:在第一次使用的地方声明变量;当你不知道该如何应对异常时,就把它们抛出。另一个则纯粹出于风格上的规范(使用args,而非argv;使用i++,而非++i)。我不想说遵循这些规则会使你的代码运行得更快,而且只有其中的少数规则才会帮助你避免 Bug。然而,在帮助你成为一名地道的Java程序员时,所有这些规则都是有必要的。
    不管怎样,说话(或写代码)时没有外地口音会使其他人更尊重你,更注意你的话,甚至向你说得更多。另外,地道使用Java程序设计语言确实比说地道的法语、汉语或英语要容易得多了。一旦你学会了这种语言,花费额外的力气把它讲得地道些是值得的。