编写高效的JAVA程序--编程规范

时间:2022-01-01 20:09:29

基本篇

 

 

本文介绍的JAVA规则的说明分为5个级别,级别1是最基本也是最重要的级别,在今后将陆续写出其他的规则。遵守了这些规则可以提高程序的效率、使代码有更好的可读性等。

1 避免使用NEW关键字来创建String对象。

把一个String常量copyString 对象中通常是多余、浪费时间的

Public class test{

Public void method(){

System.out.print (str);

}

private String str = new String ("1"); //这里新建对象是完全没有必要的

private String str2=2 //正确的应该如此

}

参考:Joshua Bloch: "Effective Java - Programming Language Guide"

 

 

2 避免使用不必要的嵌套。

过多的嵌套会使你的代码复杂化,减弱可读性。

Public class test {

String add (){

Int c=(a=a+b)+b; //过于复杂

Return c

}

 

}

 

参考:http://java.sun.com/docs/codeconv/html/CodeConventions.doc9.html#177

 

3 避免在同一行声明不同类型的多个变量

这样可以使程序更加清晰,避免混乱

private int index, index1[];

正确的应该如此:

private int index;

private int index1[];

 

 

参考:http://java.sun.com/docs/codeconv/html/CodeConventions.doc5.html#2992

 

(4) 在每一行里写一条语句

这条规则不包括for语句:比如:'for (int i = 0; i < 10; i++) x--;’可以增加代码的可读性。

public class OSPL {

int method (int a, int b) {

int i = a + b; return i; // 可读性不强

}

正确的:

public class OSPLFixed {

int method (int a, int b) {

int i = a + b;

return i;

}

}

参考:Section 7.1 of http://java.sun.com/docs/codeconv/html/CodeConventions.doc6.html#431

 

 

5)经常从finalize ()中调用super.finalize ()

这里的finalize ()java在进行垃圾收集的时候调用的,和finally不一样。如果你的父类没有定义finally()的话,你也应该调用。这里有两个原因:(1)在不改变代码的情况下能够将父类的finally方法加到你的类中。 2)以后你会养成习惯调用父类的finally方法,即使父类没有定义finally方法的时候。

正确的方法应该如此:

public class parentFinalize {

protected void finalize () throws Throwable {

super.finalize(); // FIXED

}

 

参考:"The Java Programming Language" by Ken Arnold and James Gosling, page 49.

 

6 不要在finalize ()中注销listeners

不要再finalize ()方法中中注销listenersfinalize ()只有再没有对象引用的时候调用,如果listenersfinalize()方法中去除了,被finalize的对象将不会在垃圾收集中去除。

public void finalize () throws Throwable {

bButton.removeActionListener (act);

}

 

(7) 不要显式的调用finalize ()方法

虽然显式的调用这个方法可以使你确保你的调用,但是当这个方法收集了以后垃圾收集会再收集一次。

 

public class T7 { public void finalize() throws Throwable { close_resources (); super.finalize (); } public void close_resources() {}}class Test { void cleanup () throws Throwable { t71.finalize(); // 调用 t71 = null; } private t71 = new T7 ();}

 

对于这样的调用我们应该自己创建一个释放的方法,做最初finalize ()所作的事情,当你每次想显式的调用finalize ()的时候实际上调用了释放方法。然后再使用一个判断字段来确保这个方法只执行一次,以后再调用就没关系了。

 

public class T7 { public synchronized void release () throws Throwable{ if (!_released) { close_resources (); // do what the old 'finalize ()' did _released = true; } } public void finalize () throws Throwable { release (); super.finalize (); } public void close_resources() {} private boolean _released = false;}class TestFixed { void closeTest () throws Throwable { t71 .release (); // FIXED t71 = null; } private T7 t71 = new T7 ();}

 

 

参考:Nigel Warren, Philip Bishop: "Java in Practice - Design Styles and Idioms

for Effective Java". Addison-Wesley, 1999. pp.110-111

 

 

(8)不要使用不推荐的API

尽量使用JDK1.3推荐的API。在类和方法或者java组件里有很多方法是陈旧的或者是可以选择的。有一些方法SUN用了"deprecated“标记。最好不要使用例如:

private List t_list = new List ();

t_list.addItem (str);

如果查一下javadoc的话,会发现建议用add()来代替addItem()

参考:http://java.sun.com/j2se/1.3/docs/api/index.html

 

9)为所有序列化的类创建一个'serialVersionUID'

可以避免从你各种不同的类破坏序列的兼容性。如果你不特别制订一个UID的话,那么系统为自动产生一个UID(根据类的内容)。如果UID在你新版本的类中改变了,即使那个被序列化的类没改变,你也不能反序列化老的版本了。

 

public class DUID implements java.io.Serializable { public void method () {}}

在里面加一个UID,当这个类的序列化形式改变的时候,你也改变这个UID就可以了。

 

public class DUIDFixed implements java.io.Serializable { public void method () {} private static final long serialVersionUID = 1; }

 

 

参考:Joshua Bloch: "Effective Java - Programming Language Guide"

Addison Wesley, 2001, pp. 223

 

10)对于private常量的定义

比较好的做法是对于这样的常量,加上final标记,这样的常量从初始化到最后结束值都不会改变。

private int size = 5;

改变后的做法是:

private final int size = 5;

 

(11)避免把方法本地变量和参数定义成和类变量相同的名字。

这样容易引起混扰,建议把任何的变量字都定义成唯一的。这样看来,SCJP里的那些题目在现实中就用不到了:)

 

public void method (int j) { final int i = 5; // VIOLATION } private int j = 2;

 

建议:

public void method (int j1) { final int i = 5; // VIOLATION } private int j = 2;

 

中级篇

 

 

 

 

本文介绍的JAVA规则的说明分为3个主要级别,中级是平时开发用的比较多的级别,在今后将陆续写出其他的规则。遵守了这些规则可以提高程序的效率、使代码又更好的可读性等。

1 finally方法里关掉input或者output 资源

再方法体里面定义了input或者output流的话,需要在finally里面把它关掉。

以下这几种调用不需要遵守这条规则,因为colse()方法不起作用:)

java.io.StringWriter java.io.ByteArrayOutputStream java.io.ByteArrayInputStream

如果再方法返回的时候没有调用close()方法来释放input()output()的资源的话,会导致一个系统资源泄漏。而且在任何情况下都要确定在返回全调用了close() 方法,包括出现异常的时候。所以需要在finally方法里面加入这个方法。这样就保证了在任何情况下都会关闭资源。

错误示例:

public class CIO {

public void method (java.io.File f) {

java.io.FileInputStream fis = null;

try {

fis = new java.io.FileInputStream (f);

fis.read ();

fis.close ();

} catch (java.io.FileNotFoundException e1) {

System.out.println("File not found");

} catch (java.io.IOException e2) {

System.out.println("I/O Exception");

}

// 如果出现异常,这里就不能保证关闭资源。

}

}

修正后的代码:

public class CIOFixed {

public void method (java.io.File f) {

java.io.FileInputStream fis = null;

try {

fis = new java.io.FileInputStream (f);

fis.read ();

} catch (java.io.FileNotFoundException e1) {

System.out.println("File not found");

} catch (java.io.IOException e2) {

System.out.println("I/O Exception");

} finally {

if (fis != null) {

try {

fis.close ();

} catch (java.io.IOException e) {

System.out.println("I/O Exception");

}

}

}

}

}

 

 

 

2 else的注意问题.

一般总认为如果if语句只有一句的话,那么{}就是可要可不要的了。可是如果ifelse嵌套的话,就不一样了,{}是必需的

错误示例:

if (i < 5)

if (i < 2)

i++;

else

i--;

修改后:

if (i < 5) {

if (i < 2)

i++;

}

else {

i--;

}

 

3 不要再catch()块里什么代码也不放

catch()块里面放入一些错误处理代码是一个好的习惯。但是如果catch()里面有有关javadoc 的代码,那也是可以的。

错误示例:

try {

System.in.read ();

} catch (java.io.IOException e) {

// 错误

}

 

正确:

try {

System.in.read ();

} catch (java.io.IOException e) {

System.out.println("Descriptive error");

}

参考:Joshua Bloch: "Effective Java - Programming Language Guide".

Addison-Wesley, 2001, pp. 187

 

4 不要在if条件里面附值

如果这样做的话,系统会报告错误。在java的很多条件声明里面用附值是很不明智的,而且系统也会报告错误。很容易引起异常。遵守这条规者能够使维护简单,避免不一致。

错误示例:

if (b = true)

正确的:

if (b == true)

参考:Section 10.4 of http://java.sun.com/docs/codeconv/html/CodeConventions.doc9.html#547

 

(5) for语句需要循环体。

如果没有{}的话,for语句只会执行一次!

错误示例:

for (i = 0; i < 10; i++) ;

System.out.println (i);

这里print() 只会执行一次。

正确:

for (i = 0; i < 10; i++) { // FIXED

System.out.println (i);

}

 

5 不要把方法定义成main().

java里,main()方法是一个特别的方法。所以在自己定义方法的时候不要定义这样的名字,以免引起混扰。

 

(6)不要直接或者间接的定义'Error''Throwable'的子类

'java.lang.Error'只在JVM出现反常的时候覆盖这个方法,如果你定义了直接或者不直接的类继承了类'Error',也就指出了这个错误是JVM内部的,而不是这个类的。所以对于java编译器来说是不可见的,这样就不能检查错误的异常处理了。

'java.lang.Throwable''java.lang.Exception''java.lang.Error'的上级类,用户如果象定义异常类的话应该继承'java.lang.Exception'

错误示例:public class ABC extends Error

正确:public class ABC extends Exception

 

(7)有关"switch"语句里面的"case"问题

最好在每一个 case”里都定义一个”return”或者“break”来控制不要走到下面的 case”里去。如果一个”case”语句在代码的最后没有一个”break”或者”return”句,程序就会走到下一个”case”。如果这个”case”是最后一个的话,那就没什么问题,如果后面还有”case 的话,看起来就不太安全了。

错误示例:

switch (i) {

case 1:

x = 10;

break;

case 2:

x = 20;

default:

a = 40;

break;

正确:

switch (i) {

case 1:

x = 10;

break;

case 2: // VIOLATION

x = 20;

break;

default:

x = 40;

break;

 

8)建议不要使用'System.getenv ()'

不建议使用'System.getenv ()',这个方法看起来很好用,不过并不是所有的系统都有环境变量的。不用这个方法也可能带来一些不方便。

错误示例:

void method (String name) {

System.getenv (name); // 可以用其他方法来代替

}

如果不用这个方法,我们可以用其它的方法来代替。比如:'System.getProperty ()’,'getTypeName ()'等,这也可以找到java的系统属性。

参考:David Flanagan: "Java in a Nutshell". O'Reilly

November, 1999: Third Edition, pp.190-192

 

9)不要使用’/n’或者'/r'来分行

这两个标记看来很普遍,特别是’/n’。我们经常用来作为分行用。但是不同的系统用不同的分行字符,所以这些字符在某些意义上违背了java的平台无关性。

错误示例:

System.out.println("Hello/n" + name);

我们可以用其它的一些方法来代替,比如println(),这个方法在不同的系统平台上都起到相同的作用。后者推荐大家用这个方法:System.getProperty("line.separator")

参考:David Flanagan: "Java in a Nutshell". O'Reilly,

November 1999: Third Edition, pp. 191-192

 

(10) 使所有的内部类"private".

Java允许一个类包含另外一个类,带是Java byte code没有这个概念。类被编译器解释成package-private类。从更深的程度来说,包含类的任何内部私有对象能被内部类访问的也能被同一个包内的其他类访问。

错误示例:

public class INNER {

class INNER_Class {

void setValue(int i) {

_value = i; // 现在包就可以访问了

}

}

private int _value;

}

所以需要加上private class INNER_Class

参考:Statically Scanning Java Code: Finding Security Vulnerabilities.

John Viega, Gary McGraw, Tom Mutdosch, and Edward W. Felten

IEEE SOFTWARE September/October 2000

 

11)不要使接口序列化

如果一个字节数组包含了一个被序列化的对象。攻击者就能读到这个对象的内部状态合字段(包括private的)。

错误示例:

public interface sample extends java.io.Serializable

 

开发篇

 

本文介绍的JAVA规则的说明分为3个主要级别,本篇抛弃了平时开发中很少遇到的情况,那些用得比较少的以后再高级篇里面出现。并有六个有用的国际软件开发重要注意的有关String的问题,遵守了这些规则可以提高程序的效率、使代码又更好的可读性等。

1 如果有JDBC连接没有关掉的话,需要在"finally"方法中关掉

如果数据库连接失败或者是没有释放连接,看上去无关紧要。但是其他的用户就需要用更长的时间等待连接,这样数据库利用效率就会下降。确保你的代码在任何情况下,包括出错或者程序异常终止的情况下都释放数据库连接。在"finally"方法中关掉连接,就可以确保这一点。

错误示例:

try {

Statement stmt = con.createStatement();

} catch(SQLException e) {

e.printStackTrace();

}

正确示例:

try {

Statement stmt = con.createStatement();

} finally {

if (con != null && !con.isClosed()) {

con.close();

}

}

 

 

 

 

 

 

2 尽量避免使用'Thread.resume ()', 'Thread.stop ()', 'Thread.suspend ()' 'Runtime.runFinalizersOnExit ()' 方法。

这些方法在平时的开发或者是教科书里面也有用到过,但是这些方法会导致四锁的倾向。一下有充足的资料来说明为什么不建议用上述方法。

参考:1."java.lang.Thread" in the JDK API documentation

2.http://java.sun.com/j2se/1.3/docs/guide/misc/threadPrimitiveDeprecation.html

3.Paul Hyde: "Java Thread Programming"

Sams, ISBN: 0-672-31585-8 pp. 270

 

3 在表示长整常量的时候,用L来代替l.

因为l很容易和1混一起。

错误示例:

long temp = 23434l;

正确示例:

long temp = 23434L;

参考:Ken Arnold, James Gosling: "The Java Programming Language Second Edition"Addison Wesley, 1997, pp.108

 

4 最好在jsp开头写一条注释

jsp文件头上面写一条注释,这样可以帮助别人来理解你的代码。这条规则不仅适用于jsp,更是用于任何开发的文档。

正确示例:<%-- JSP comment --%>

 

 

(5)明确的初始化一个构造类里面的所有的字段

因为没有初始化的字段会是一个潜在的bug,所以最好初始化类里面的所有的字段。特别是静态的字段,最好在一开始就分配一个初始值

错误示例:

public class CSI {

public CSI () {

this (12);

k = 0;

}

 

public CSI (int val) {

j = val;

}

 

private int i = 5;

private int j;

private int k;

}

 

正确示例:

public class CSIFixed {

public CSIFixed () {

this (12);

}

 

public CSIFixed (int val) {

j = val;

k = 0;

}

 

private int i = 5;

private int j;

private int k;

}

参考:http://www.ambysoft.com/javaCodingStandards.pdf

 

5 国际化开发建议:逻辑操作符不要再一个单个的字符的前面或者后面

一个单个字符的前后不要用逻辑操作符,如果代码要在一个国家环境中运行的话。我们可以使用字符比较方法,这些方法使用统一字符比较标准来定义字符的属性的。

错误示例:public class CLO {

public boolean isLetter (char ch) {

boolean _isLetter = ( ch >= 'a' && ch <= 'z') //错误

|| (ch >= 'A' && ch <= 'Z');

return _isLetter;

}

}

 

正确示例:

public class CLOFixed {

public boolean isLetter (char ch) {

boolean _isLetter = Character.isLetter(ch);

return _isLetter;

}

}

参考: http://java.sun.com/docs/books/tutorial/i18n/intro/checklist.html

更多的字符比较方法请参考:http://java.sun.com/docs/books/tutorial/i18n/text/charintro.html

 

 

6 国际化开发建议:不要对日期对象使用'Date.toString ()'

不要使用'Date.toString ()'方法,日期格式对于地区和语言不同的国家来说是不一样的,务必不要使用。

错误示例:'DateFormat'类提供了一个预定义的格式类型来指定本地的格式。

public void printToday () {

Date today = new Date ();

String todayStr = today.toString ();

System.out.println (todayStr);

}

正确示例:

public void printToday () {

Locale currentLocale = Locale.getDefault ();

DateFormat dateFormatter = DateFormat.getDateInstance (

DateFormat.DEFAULT, currentLocale);

Date today = new Date ();

String todayStr = dateFormatter.format (today);

System.out.println (todayStr);

}

参考:http://java.sun.com/docs/books/tutorial/i18n/intro/checklist.html

http://java.sun.com/docs/books/tutorial/i18n/format/dateFormat.html

 

7 国际化开发建议:不要对数字变量使用'toString ()'方法

在全球化的开发中,不要对数字变量使用'toString ()'方法,对于java.lang.Number的任何子类都适用。包括:BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, and Short.对于这样的情况,java里也与定义了"NumberFormat"方法来格式化。

错误示例:

public class NTS {

public void method (Double amount) {

String amountStr = amount.toString ();

System.out.println (amountStr);

}

}

正确示例:

public class NTSFixed {

public void method (Double amount) {

Locale currentLocale = Locale.getDefault ();

NumberFormat numberFormatter =

NumberFormat.getNumberInstance (currentLocale);

String amountStr = numberFormatter.format (amount); //

System.out.println (amountStr + ' ' + currentLocale.toString ());

}

}

参考:http://java.sun.com/docs/books/tutorial/i18n/intro/checklist.html

http://java.sun.com/docs/books/tutorial/i18n/format/numberFormat.html

 

 

8 国际化开发建议:不要使用'String.equals ()'方法

建议不要使用'String.equals ()'方法,因为在统一字符比较标准中不一定按照相关的顺序来比较。'Collator'提供的预定义整理规则来排序stringCollator类调用'getInstance ()'方法,一般来说,可以为默认的本地创建一个Collator。例如:Collator myCollator = Collator.getInstance ();创建Collator的时候你也可以指定一个特殊的locale。例如:Collator myFrenchCollator = Collator.getInstance (Locale.FRENCH);然后就可以调用'Collator.compare ()'来执行一个本地的字符比较myCollator.compare (s1,s2);从这里可以了解更多的有关Collator类的信息:http://java.sun.com/docs/books/tutorial/i18n/text/collationintro.html

 

 

 

错误示例:

public class SE {

public boolean compstr (String s1, String s2) {

boolean b = (s1.equals (s2));

return b;

}

}

正确示例:

public class SEFixed {

public boolean compstr (String s1, String s2) {

Collator myCollator = Collator.getInstance ();

boolean b = (myCollator.compare(s1,s2) == 0);

return b;

}

}

 

参考:http://java.sun.com/docs/books/tutorial/i18n/intro/checklist.html

http://java.sun.com/docs/books/tutorial/i18n/text/locale.html

 

9 国际化开发建议:不要使用'StringTokenizer()'方法

错误示例:StringTokenizer st = new StringTokenizer(str);

可以从这里得到更多的信息:‘

参考:http://java.sun.com/docs/books/tutorial/i18n/intro/checklist.html

 

10 国际化开发建议:不要使用'Time.toString ()'方法

因为时间的格式各个国家也不一样。如果你使用日期格式类,你的应用就能够在世界上各个地方正确的显示时间和日期了。首先,用'getTimeInstance ()'方法创建一个formatter。然后,调用'format ()'方法。

错误示例:

public class TTS {

public void printTime (Time t1) {

String timeStr = t1.toString ();

System.out.println (timeStr);

}

}

正确示例:

import java.sql.Time;

import java.text.DateFormat;

import java.util.Locale;

 

public class TTSFixed {

public void printTime (Time t1) {

DateFormat timeFormatter = DateFormat.getTimeInstance(

DateFormat.DEFAULT, Locale.getDefault ());

String timeStr = timeFormatter.format(t1);

System.out.println (timeStr);

}

}