1. 文件名(File Names)
1.1 文件后缀(File Suffixes)
Java源文件以.java结尾。
2. 文件组织(File Orgnization)
一个文件由被空行分割而成的段落以及标识每个段落的可选注释共同组成。超过2000行的程序难以阅读,应该尽量避免。“Java源文件范例”提供了一个页面布局合理的Java程序范例。
2.1 Java源文件(Java Source Files)
Java源文件必须以utf-8编码。除了注释以外,一般不允许出现非ASCII字符。
每个Java源文件都包含一个单一的公共类或接口。若私有类和接口与一个公共类相关联。可以将它们和公共类放入同个源文件。公共类必须是这个文件中的第一个类和接口。
2.1.1 开头注释(Beginning Comments)
所有的源文件都应该在开头有一个C语言风格的注释,其中必须列出版权声明,还可包括版本信息和日期等:
/*
* Copyright 2014-2015 www.10101111.com All Rights Reserved..
*/
2.1.2 包和引入(Package and Import Statements)
在多数Java源文件中,第一个非注释行是包语句行。在它之后可以跟引入语句。例如:
package java.awt;
import java.awt.peer.CanvasPeer;
我们不允许使用包含通配符(*)的引入语句,也就是说只能引入具体类而不能引入整个包。
// NOT ALLOWED
import foo.*;
导入包的顺序为:java基础包,javax基础包,android包。
import java.a.B;
import javax.a.B;
import android.a.B;
import org.apache.a.B;
2.1.3 类和接口声明(Class and Interface Declarations)
下表描述了类和接口声明的免修部分以及它们出现的先后次序。参见“Java源文件范例”中一个包含注释的例子。
类/接口声明的各部分 | 注解 |
类/接口文档注释(/**…*/) | 该注释中所包含的信息,参见“文档注释”。强调,必须包含@author信息。 |
class/interface的声明 | |
类/接口实现的注释(/*…*/) | 如果有必要的话,该注释应包含任何有关整个类或接口的信息,而这些信息又适合作为类/接口文档注释。 |
类的(static)变量 | 首先是类的public变量,随后是protected变量,再后是包一级别的变量(没有访问修饰符),最后是private变量。 |
实例变量 | 首先是public变量,随后是protected变量,再后是包一级别的变量(没有访问修饰符),最后是private变量。 |
构造器 | |
方法 | 这些方法应该按功能,而非作用域或访问权限,分组。 |
3. 缩进排版(Indentation)
3.0 缩进和空格
4个空格作为缩进排版的一个单位,不允许使用制表符。
3.1 行长度
尽量避免一行长度超过120个字符,因为很多终端和工具不能很好处理之。注意:用于文档是的例子应该使用更短的行长,长度一般不超过80个字符。
3.2 换行(Wrapping Lines)
当一个表达式无法容纳在一行内时,可以依据如下一般规则断开之:
· 在一个逗号后面断开。
· 在一个操作符前面断开。
· 宁可选择较高级别的(higher-level)的断开,而非较低级别(lower-level)的断开。
· 新的一行应该与上一行同一级别表达式的开头处对齐。
· 如果以上规则导致你的代码混乱或者使你的代码都堆挤在右边,那就代之以缩进8个空格。
以下是断开方法的一些例子:
someMethod(longExpression1, longExpression2, longExpression3,
longExpression4, longExpression5); // 8个空格
var = someMethod1(Expression1,
someMethod2(longExpression2,
longExpression3));
以下是两个断开算术表达式的例子。前者更好,因为断开处位于括号表达式的外边,这是个较高级别的断开。
longName1 = longName2 * (longName3 + longName4 - longName5)
+ 4 * longname6; // PREFER
longName1 = longName2 * (longName3 + longName4
- longName5) + 4 * longName6; //AVOID
以下是两个缩进方法声明的例子。前者是常规情形,后者若使用常规的缩进方式将会使第二行和第三行移得很靠右,所以代这以缩进8个空格。
// CONVENTION INDENTATION
someMethod(int anArg, Object anotherArg,
String yetAnotherArg,
Object andStillAnother) {
……
}
// INDENT 8 SPACES TO AVOID VERY DEEP INDENTS
private static synchronized horkingLongMethodName(int anArg,
Object anotherArg, String yetAnotherArg,
Object andStillAnother) {
……
}
if语句的换行通常使用8个空格的规则,因为常规缩进(4个空格)会使语句看起来比较费劲。比如:
//DON’T USE THIS INDENTATION
if ((condition1 && condition2)
|| (condition3 && condition4)
|| !(condition5 && condition6)) { //BAD WRAPS
doSomethingAboutIt(); // MAKE THIS LING EASY TO MISS
}
// USE THIS INDENTATION INSTEAD
if ((condition1 && condition2)
|| (condition3 && condition4)
|| !(condition5 && condition6)) {
doSomethingAboutIt();
}
// OR USE THIS
if ((condition1 && condition2) || (condition3 && condition4)
|| !(condition5 && condition6)) {
doSomthingAoutIt();
}
这里有三种可行的方法用于处理三元运算表达式:
alpha = (aLongBooleanExpression) ? beta : gamma;
alpha = (aLongBooleanExpression) ? beta
: gamma;
alpha = (aLongBooleanExpression)
? beta
: gamma;
4. 注释(Comments)
Java程序有两类注释:实现注释(implementation comments)和文档注释(document comments)。实现注释是那些在C++中见过的,使用/*…*/和//界定的注释。文档注释(被称为“doc comments”)是Java独有的,并由/**…*/界定。文档注释可以通过javadoc工具转换成HTML文件。
实现注释用以注释代码或或者实现细节。文档注释从实现自由(implemtentation-free)的角度描述代码的规范。它可以被那些手头没有源码的开发人员读懂。
注释应被用来给出代码的总括,并提供代码自身没有提供的附加信息。注释应该仅包含与阅读和理解程序有关的信息。例如,相应的包如何被建立或位于哪个目录下之类的信息不应包括在注释中。
在注释里,对设计决策中重要的或者不是显而易见的地方进行说明是可以的,但应避免提供代码中已清晰表达出来的重复信息,多余的注释很容易过时。通常应避免那些代码更新就可能过时的注释。
注意:频繁的注释有时反映出代码的低质量。当你觉得被迫要加注释的时候,考虑一下重写代码使其更清晰。
注释不应写在用星号或字符画出来的大框里。注释不应包括诸如制表符和回退符之类的特殊字符。
4.1 实现注释的格式(Implementation Comment Formats)
程序可以有4种实现注释的风格:块(Block),单行(single-line),尾端(trailing)和行末(end-of-line)。
4.1.1 块注释
块注释通常用于提供对文件,方法,数据结构和算法的描述。块注释被置于每个文件的开始处以及每个方法之前。它们也可以被用于其他地方,比如方法的内部。在功能和方法内部的块注释应该和它们所描述的代码具有一样的缩进格式。
块注释之首应该有一个空行,用于把块注释和代码分割开来,比如:
/*
* Here is a block comment.
*/
4.1.2 单行注释(Single-Line Comments)
短注释可以显示一行内,并与其后的代码具有一样的缩进层级。如果一个注释不能在一行内写完,就该块注释(参见“块注释”)。单行注释之前应该有一个空行。以下是一个Java代码中单行注释的例子:
if (condition) {
/* Handle the condition. */
……
}
4.1.3 尾端注释(Trailing Comments)
极短的注释可以与它们所要描述的代码位于同一行,但是应该有足够的空白(至少一个空格)来分开代码和注释。若有多个短注释出现于大段代码中,它们应该具有相同的缩进。
以下是一个Java代码中尾端注释的例子:
if (a ==2) {
return TRUE; /* special case */
} else {
return isPrime(a); /* works only for odd a */
}
4.1.4 行末注释(End-Of-Line Comments)
注释界定符//,可以注释掉整行或者一行中的一部分。它一般不用于连续多行的注释文本;然而,它可以用来注释掉多行的代码段。以下是所有三种风格的例子:
if(foo > 1) {
// Do a double-filp.
……
} else {
return false; // Expalin why here.
}
//if (bar > 1) {
//
// // Do a triple-flip.
// ...
//}
//else {
// return false;
//}
4.2 文档注释(Documentation Comments)
注意:此处描述的注释格式之范例,参见“Java源文件范例”
若想了解更多,参见“How to Write Doc Comments for Javadoc”,其中包含了有关文档注释标记的信息(@return,@param,@see):
http://java.sun.com/javadoc/writingdoccomments/index.html
若想了解有关文档注释和javadoc的详细资料,参见javadoc的主页:
http://java.sun.com/javadoc/index.html
文档注释描述Java的类、接口、构造器、方法,以及字段(field)。每个文档注释都会被置于注释界定符/**…*/之中,一个注释对应一个类、接口或成员。该注释应位于声明之前:
/**
* The Example class provides …
*
* @author FirstName Lastname (account@10101111.com)
*/
public class Example { …
注意:顶层(top-level)的类和接口是不缩进的,而其成员是缩进的。描述类和接口的文档注释的第一行会被置于注释的第一行(/ **)不需要缩进;随后的文档注释每行都缩进1格(使星号纵向对齐)。成员,包括构造函数在内,其文档注释的第一行缩进4格,随后每行都缩进5格。
若你想给出有关类、接口、变量或方法的信息,而这些信息又不适合写在文档中,则可使用实现块注释(见5.1.1)或紧跟在声明后面的单行注释(见5.1.2)。例如,有关一个类实现的细节应放入紧跟在类声明后面的实现块注释中,而不是放在文档注释中。
文档注释不能放在一个方法或构造器的定义块中,因为Java会将位于文档注释之后的第一个声明与其相关联。
5. 声明(Declaration)
5.1 每行声明变量的数量(Number Per Line)
推荐一行一个声明,因为这样以利于写注释。亦即,
int level; // indentation level
int size; // size of table
要优于,
int level, size;
不要将不同类型变量的声明放在同一行,例如:
int foo, fooarry[]; // WRONG!
注意:上面的例子中,在类型和标识之间放了一个空格,另一种被允许的替代方法是多行变量注释的对齐:
int level; // indentation level
int size; // size of table
Object currentEntry; // currently selected table entry
5.2 初始化(Initialization)
尽量在声明局部变量的同时进行初始化。唯一 不这么做理由是变量的初始值依赖于某些先前发生的计算。
5.3 布局(Placement)
建议只在代码块的开始处声明变量(一个块可以指任何被包含在大括号“{”和“}”中间的代码,也可以是逻辑上分块的代码)。通常不要在首次用于该变量时才声明之,这会把注意力不集中的程序员搞糊涂,同时会妨碍代码在该作用域内的可移植性。
void myMethod() {
int int1 = 0;
if (condition) {
int int2 = 0;
…
}
}
该规则的一个例外是for循环的索引变量
for (int i = 0; I < maxLoops; i++) { … }
避免声明的局部变量覆盖上一级声明的变量。例如,不要在内部代码块中声明相同的变量名:
int count;
…
myMethod() {
if (condition) {
int count = 0; // AVOID!
…
}
…
}
5.4 类和接口的声明(Class and Interface Declarations)
当编写类和接口时,应该遵守以下格式规则:
· 在方法名与其参数列表之前的左括号“(”间不要有空格。
· 左大括号“{”位于声明语句同行的末尾。
· 右大括号“}”另起一行,与相应的声明语句对齐,除非是一个空语句,“}”应紧跟在“{”之后。
· class Sample extends Object {
· int ivar1;
· int ivar2;
·
· Sample(int i, int j) {
· ivar1 = i;
· ivar2 = j;
· }
·
· int emptyMethod() {}
·
· …
· }
· 方法与方法之间 以空行分隔。
6. 语句(Statements)
6.1 简单语句(Simple Statement)
每行至多包含一条语句,例如:
argv++; // Correct
argc--; // Correct
argv++; argc--; // AVOID!
6.2 复合语句(Compound Statements)
复合语句是包含在大括号中的语句序列,形如“{ statements }”。例如下面各段。
· 被括其中的语句应该较之复合语句缩进一个层次。
· 左大括号“{”应位于复合语句起始行的行尾;右大括号“}”应另起一行并与复合语句首行对齐。
· 大括号可以被用于所有语句,包括单个语句,只要这些语句是诸如if–else或for控制结构的一部分。这样便于添加语句而无需担心由于忘了加括号而引入bug.
6.3 返回语句(return Statements)s
一个带返回值的return语句不使用小括号“()”,除非它们以某种方式使返回值更显见。例如:
return;
return myDisk.size();
return (size ? size ; defaultSize);
6.4 if, if-else, if else-if else语句(if, if-else, if else-if else Statements)
if-else语句应该具有如下格式:
if (condition) {
statements;
}
if (condition) {
statements;
} else {
statements;
}
if (condition) {
statements;
} else if (condition) {
statements;
} else if (condition) {
statements;
}
注意:if语句问题用“{”和“}”括起来,避免使用如下容易引起错误的格式:
if (condition) // AVOI! THIS OMITS THE BRACES {}!
statement;
6.5 for语句(for Statements)
一个for语句应该具有如下格式:
for (initialization; condition; update) {
statements;
}
一个空的for语句(所有工作都在初始化,条件判断,更新子句中完成)应该具有如下格式:
for (initialization; condition; update);
当在for语句的初始化或更新子句中使用逗号时,避免因使用三个以上变量,而导致复杂度提高。若需要,可以在for循环之前(为初始化子句)或for循环末尾(为更新子句)使用单独的语句。
6.6 while语句(while Statements)
一个while语句应该具有如下格式:
while (condition) {
statements;
}
一个空的while语句应该具有如下格式:
while (condition);
6.7 do-while语句(do-while Statements)
一个do-while语句应该具有如下格式:
do {
statements;
} while (condition);
6.8 switch语句(switch Statements)
一个switch语句应该具有如下格式:
switch (condition) {
case ABC:
statements;
/* falls through */
case DEF:
statements;
break;
case XYZ:
statements;
break;
default:
assert false : condition; // Or throw error.
}
每当一个case顺着往下执行时(因为没有break语句),通常应在break语句的位置添加注释。上面的示例代码中就包含注释/* falls through */。
每个switch语句,必须有对应的default语句。如果执行不到,可以加上assert语句。
6.9 try-catch语句(try-catch Statements)
一个try-catch语句应该具有如下格式:
try {
statements;
} catch (ExceptionClass e) {
statements;
}
一个try-catch语句后面也可能跟着一个finally语句,不论try代码块是否顺利执行完,它都会被执行。
try {
statements;
} catch (ExceptionClass e) {
statements;
} finally {
statements;
}
7. 空白(White Space)
7.1 空行(Blank Lines)
空行将逻辑相关的代码段分隔开,以提高可读性。
下列情况应总是使用两个空行:
· 一个源文件的两个片段(section)之间。
· 类声明和接口声明之间。
下列情况应该总是使用一个空行:
· 两个方法之间。
· 方法内的局部变量和方法的第一条语句之间。
· 块注释(参见“5.1.1”)或单行注释(参见5.1.2)之前。
· 一个方法内的两个逻辑段之间,用以提高可读性。
7.2 空格(Blank Spaces)
下列情况应该使用空格:
· 一个紧跟着括号的关键应该被空格分开,例如:
· while (true) {
· ….
· }
注意:空格不应该置于方法名与其左括号之间,这将有助于区分关键字和方法调用。
· 空白应该位于参数列表中逗号的后面。
· 所有的二元运算符,除了“.”,应该使用空格将之与操作数分开。一元操作符和操作数之间不应该加空格,比如:负号(“-”),自增(“++”)和自减(“--”)。例如:
· a += c + d;
· a = (a + b) / (c * d);
·
· while (d++ = s++) {
· n++;
· }
· printSize(“size is ” + foo + “
”);
· for语句中的表达式应该被空格分开,例如:
· for (expr1; expr2; expr3)
· 强制转型后应该跟一个空格,例如:
· myMethod((byte) aNum, (Object) x);
· myMethod((int) (cp + 5), ((int) (i + 3)) + 1);
8. 命名规范(Naming Convention)
命名规范使程序更易读,从而更易于理解。它们也可以提供一些有关标识符功能的信息,以助于理解代码。例如,不论它是一个常量、包,还是类(如9.7的例子所示)。
8.1包(Packages)
一个唯一包名的前缀总是全部小写的ASCⅡ字母并且是一个顶级域名,通常是com,gov,edu,mil,net,org,或1981年ISO 3166标准所指定的标识的国家的英文双字符代码。包名的后续部分根据不同机构各自内部的命名规范而不尽相同。这类命名规范可能以特定目录名的组成来区分部门(department),项目(project),机器(machine),或注册名(login names)。
例如:
com.sun.eng
com.zuche
8.2类(Classes)
类名是一个名词,采用大小写混合方式,每个单词的首字母大写。尽量使你的类名简洁而富于描述。使用完整单词,避免缩写词(除非该缩写词被广泛的使用,如URL,HTML)。例如:
class Raster
class ImageSprite
8.3接口(Interfaces)
接口名称的大小写方式与类名称一致。
interface RasterDelegate
interface Storing
8.4方法(Methods)
方法名是一个动词,采用大小写混合方式,第一个单词的首字母小写,其后单词的首字母大写。例如:
run()
runFase()
getBackground()
8.5变量(Variables)
除了变量名外,所有实例,包括类,类常量,均采用大小写混合的方式,第一个单词的首字母小写,其后单词的首字母大写。变量名不应以下划线("_")或美元符号("$")开头,尽管这在语法上是允许的。
实例变量名应简短且富于描述。变量名的选用应该易于记忆,即,能够指出其用途。尽量避免单个字符的变量名,除非是一个性临时变量。如被用作循环的i,j,k。
for (int i = 0; i < size; ++i)
float myWidth
Android变量命名规则: 非公共的实例变量应该以 m 开头; 类变量(静态变量)应该以 s 开头; 其他类型变量与通用规范一致。
public class MyClass {
public static final int SOME_CONSTANT = 42;
public int publicField;
private static MyClass sSingleton;
int mPackagePrivate;
private int mPrivate;
protected int mProtected;
}
8.6常量(Constants)
大小写规则和变量名相似,除了前面需要一个下划线隔开。尽量避免ANSI常量,容易引起错误。例如:
static final int MIN_WIDTH = 4
static final int MAX_WIDTH = 999
static final int GET_THE_CPU = 1
9. 编程惯例(Programming Practices)
9.1 提供对实例以及类变量的访问控制(Providing Access to Instance and Class Variables)
若没有足够的理由,不要把实例或类类变量声明为public。通常,实例变量无需显式的设置(set)和获取(gotten),通常这作为方法调用的边缘效应(side effect)而产生。
一个具有public实例变量的恰当例子,是类仅作为数据结构,没有行为。亦即,若你要使用一个结构(struct)而非一个类(如果Java支持结构的话),那么把类的实例变量声明为public是合适的。
Android变量访问控制:Android的开发通常允许公共的实例变量。
// Common practice in Android development.
public class MyClass {
public int pulicField; // public field for direct access.
}
9.2 引用类变量和类方法(Referring to Class Variables and Methods)
避免用一个对象访问一个类的静态变量和方法。应该用类名替代。例如:
classMethod(); // OK
AClass.classMethod(); // OK
anObject.classMethod(); // AVOID!
9.3常量(Constants)
位于for循环中作为计数器值的数字常量,除了-1,0和1之外,不应被直接写入代码。
9.4 变量赋值(Variable Assignments)
避免在一个语句中给多个变量赋相同的值。它很难读懂。例如:
fooBar.fChar = barFoo.lchar = ‘c’; // AVOID!
不要将赋值运算符用在容易与相等关系运算符混淆的地方。例如:
if (c++ = d++) { // AVOID! (Java disallows)
….
}
应该写成
if ((c++ = d++) ! = 0) {
…
}
不要使用内嵌(embedded)赋值运算符试图提高运行时效率,这是编译器的工作。例如:
d = (a = b +c) + r; // AOVID!
应该写成
a = b + c;
d = a + r;
9.5 其它惯例(Miscellaneous Practices)
9.5.1 圆括号与运算符优先级(Parentheses and Operator Precedence)
一般而言,在含有多种运算符的表达式中使用括号来避免运算符优先级问题,是个好方法。即便运算符的优先级对你而言可能很清楚,但对其他人未必如此。你不能假设别的程序员和你一样清楚运算符的优先级。
<="" pre="" style="font-size: 10pt; display: block; color: rgb(204, 0, 0); border-color: rgb(255, 240, 240); border-style: solid; border- 1px 1px 1px 5px; padding: 4px 12px; background-color: rgb(255, 248, 248);">if (a == b && c ==d) // AVOID!
if ((a == b) && (c == d)) // RIGHT
9.5.2 返回值(Returning Values)
设法让你的程序结构符合目的。例如:
if (booleanException) {
return true;
} else {
return false;
}
应该代之以如下方法:
return booleanException;
类似地:
if (condition) {
return x;
}
return y;
应该写为:
return (condition ? x : y);
9.5.3 条件运算符“?”前的表达式(Expressions before “?” in the Conditional Operator)
如果一个包含二元运算符表达式出现在三元运算符“ ? : ”之前,那么应该给表达式添上一对圆括号。例如:
(x >= 0) ? x : -x;
9.5.4 特殊注释(Special Comments)
使用TODO来注释一个临时的或者未完成的解决方案。TODO必须包含详细的信息,例如,需要做什么,打算怎么做,为什么以后才做,等。要发布到生产环境的代码尽量减少TODO。
// TODO: Change this to use a flag instead of a constant.
如果有可能,尽量包含具体的开发者帐号,以及具体的修正日期。
// TODO(tom, Fix by Nov 2005): Change this to use a flag.
9.6 所有对象的共有方法(Methods Common to All Objects)
9.6.1 覆盖equals时请遵守通用约定
Item 8: Obey the general contract when overriding equals.
重写equals方法看起来很简单,实际上非常容易犯错误。在多数情况下我们不推荐重写equals方法。如果确实有必要,这里再强调一下Object.equals的约定:
· 自反:对于任意non-null的引用值x,x.equals(x)必须返回true。
· 对称:对于任意的non-null的值x和y,x.equals(y)和y.equals(x)必须返回相同的值。
· 传递:对于任意non-null的值,x,y,z,如果x.equals(y)返回true以及y.equals(z)返回true,那么x.equals(z)也必须返回true。
· 一致:如果equals实现中的辅助信息没有改变,对于任意的x和y,对x.equals(y)必须返回相同的值。
· 对于所有的non-null的x,x.equals(null)必须返回false。
9.6.2 覆盖equals时总要覆盖hashCode
Item 9: Always override hashCode when you override equals.
对于每一个equals方法被重写的类,你必须重写hashCode方法。请仔细阅读javadoc中关于Object.equals的描述。相等的对象必须有相等的hash值。
这里是一个错误的例子:
public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
@Override
public boolean equals(Object o) {
if (!(o instanceof PhoneNumber)) {
return false;
}
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}
// Broken - no hashCode method!
}
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
// null is returned instead of "Jenny"
m.get(new PhoneNumber(707, 867, 5309));
9.7 泛型(Generics)
9.7.1 请不要在新代码中使用原生态类型
Item 23: Don't use raw types in new code.
从1.5版本开始,java提供了泛型机制来保证类型的安全。除非为了向后兼容,不允许使用原生态类型,像List(而不是List<String>)。
下面这段代码就很容易出问题:
// Now a raw collection type - don't do this!
/**
* My stamp collection. Contains only Stamp instances.
*/
private final Collection stamps = ... ;
// Erroneous insertion of coin into stamp collection
stamps.add(new Coin( ... ));
// Now a raw iterator type - don't do this!
for (Iterator i = stamps.iterator(); i.hasNext(); ) {
Stamp s = (Stamp) i.next(); // Throws ClassCastException
... // Do something with the stamp
}
使用泛型能够很好地解决这个问题:
// Parameterized collection type - typesafe
private final Collection<Stamp> stamps = ... ;
// for-each loop over a parameterized collection - typesafe
for (Stamp s : stamps) { // No cast
... // Do something with the stamp
}
9.7.2 消除非受检警告
Item 24: Elimate unchecked warnings.
在使用泛型编程的时候,我们经常会看到编译警告:非受检的类型转换警告,非受检的函数调用警告等等。很多非受检的警告是非常容易消除的,例如:
Set<Lark> exaltation = new HashSet();
编译器会抛出警告:
Venery.java:4: warning: [unchecked] unchecked conversion
found : HashSet, required: Set<Lark>
Set<Lark> exaltation = new HashSet();
^
我们很容易消除这个警告:
Set<Lark> exaltation = new HashSet<Lark>();
关于消除非受检的警告,有如下基本规则:
· 尽可能地消除每个非受检的警告。
· 如果无法消除,必须能够证明这个警告不会引起类型安全问题,同时通过@SuppressWarnings("unchecked")来消除警告。
· SuppressWarning必须作用在尽可能小的范围。
· 每一次使用@SuppressWarnings("unchecked")时,必须注释说明为什么这样做是安全的。
9.8 枚举和注解(Enums and Annotations)
9.8.1 用enum代替int常量
Item 30: Use enums instead of int constants.
下面的代码利用了一种叫 int enum pattern 的技术,它有很多问题。它不能保证类型安全;它不能很好地转换为可读的字符串;如果数值变了的话,使用它的代码必须重新编译(严重!)。
// The int enum pattern - severely deficient!
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
我们应该用enum来实现上面的代码:
public enum Orange { NAVEL, TEMPLE, BLOOD }
注意:出于效率考虑,Android的开发允许使用int常量,但是使用时要非常小心。
9.8.2 用实例域代替序数
Item 31: Use instance fields instead of ordinals.
所有的枚举类型都有一个ordinal方法,它能够返回每个枚举常量在类型中的位置。有时候,你可能会想从这个方法中直接得到一个整数值:
// Abuse of ordinal to derive an associated value - DON'T DO THIS
public enum Ensemble {
SOLO, SEXTET, DUET, TRIO, QUARTET, QUINTET,
SEPTET, OCTET, NONET, DECTET;
public int numberOfMusicians() { return ordinal() + 1; }
}
不要从枚举的序数中得到一个整数值,如果需要,请创建一个整数字段:
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
NONET(9), DECTET(10), TRIPLE_QUARTET(12);
private final int numberOfMusicians;
Ensemble(int size) { this.numberOfMusicians = size; }
public int numberOfMusicians() { return numberOfMusicians; }
}
9.8.3 坚持使用Override注解
Item 36: Consistently use the Override annotation.
你能发现下面代码的错误吗?
public class Bigram {
private final char first;
private final char second;
public Bigram(char first, char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
}
}
如果我们加入了@Override的标注,编译器就能告诉我们错误了。
@Override public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
Bigram.java:10: method does not override or implement a method
from a supertype
@Override public boolean equals(Bigram b) {
^
9.9 方法 (Methods)
9.9.1 检查参数的有效性
Item 38: Check parameters for validaity.
大多数的方法对于传入参数的值有限制,例如,通常来说索引值必须是非负的,对象应用必须是非空的。一条总的规则是,在错误发生之前必须尽早地发现它、处理它。
如果我们能在方法执行主要逻辑之前检查到错误的参数,我们就能够及时退出,同时抛出一个合适的一场。如果错误的参数进入函数的主要执行逻辑,方法就可能抛出一个奇怪的异常。在更坏的情况下,方法可能成功返回,但是中间出现一些不可预知的结果。
对于公共方法,用@throws来注释异常。比如,这些异常可能是IllegalArgumentException或者是NullPointerException。例如:
/**
* @param m the modulus, which must be positive
* @return this mod m
* @throws ArithmeticException if m is less than or equal to 0
*/
public BigInteger mod(BigInteger m) {
if (m.signum() <= 0) {
throw new ArithmeticException("Modulus <= 0: " + m);
}
... // Do the computation
}
9.10 异常(Exceptions)
9.10.1 只针对异常的情况才使用异常
Item 57: Use exceptions only for exceptional conditions.
异常,只应该被应用于异常的情况;不能被用于正常的控制流程。这一点在很多时候会被误用,下面是一个极端的例子:
// Horrible abuse of exceptions. Don't ever do this!
try {
int i = 0;
while(true)
range[i++].climb();
} catch(ArrayIndexOutOfBoundsException e) {
}
9.10.2 对可恢复的情况使用受检异常,对编程错误使用运行时异常
Item 58: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors.
受检异常在调用者能够合适处理并恢复程序执行的情况下使用。运行时异常用来表明程序自身的错误,大多数的运行时异常都是因为调用者违反了函数的预设条件。
应用程序所有非受检的异常应该派生自RuntimeException,作为惯例,Error通常只被JVM使用。
9.10.3 抛出与抽象相对应的异常
Item 61: Throw exceptions appropriate to the abstraction.
抛出与当前方法明显无关的异常会让人十分迷惑,而且会暴露出实现的细节。这种情况通常是因为高一级的方法直接抛出底层调用的异常引起的。我们采用异常转换的方法来避免这一种情况。也就是说,高一级的方法应该捕获底层调用的异常,抛出一个更高抽象的异常。例如,下面是一段AbstractSequentialList实现代码,它需要实现List<E>#get方法。
/**
* Returns the element at the specified position in this list.
* @throws IndexOutOfBoundsException if the index is out of range
*
({@code index < 0 || index >= size()}).
*/
public E get(int index) {
ListIterator<E> i = listIterator(index);
try {
return i.next();
} catch(NoSuchElementException e) {
throw new IndexOutOfBoundsException("Index: " + index);
}
}
需要注意的是,不要滥用了异常转换。
9.10.4 每个方法抛出的异常都要有文档
Item 62: Document all exceptions thrown by each method.
关于异常的文档有以下要求:
· 必须单独地声明每个受检异常,同时准确地用@throws来说明异常条件。
· 用Javadoc的@throws来表明非受检的异常;但是不要用throws关键字在方法的声明中包括非受检的异常。
· 如果一个异常会被同一个类的很多方法抛出,在类的注释中表明这个异常是可以接受的。但是通常不推荐这么做。
9.10.5 不要忽略异常
Item 65: Don't ignore exceptions.
我们不允许像下面的代码一样忽略异常:
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
}
}
至少,在catch语句里,应该有注释说明为什么忽略异常是合适的。
/** If value is not a valid number, original port number is used. */
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
// Method is documented to just ignore invalid user input.
// serverPort will just be unchanged.
}
}
10.10.6 不要一次捕获所有异常
try {
someComplicatedIOFunction(); // may throw IOException
someComplicatedParsingFunction(); // may throw ParsingException
someComplicatedSecurityFunction(); // may throw SecurityException
// phew, made it all the way
} catch (Exception e) { // I'll just catch all exceptions
handleError(); // with one generic handler!
}
在绝大多数情况下,捕获Exception或者是Throwable都是不对的。这个非常危险,你可能会捕获一些没有想到的异常,或者是稍后加入的异常。
10. 代码范例(Code Examples)
10.1 Java源文件范例(Java Source File Example)
下面的例子,展示了如何合理布局一个包含单一公共类的Java源程序。接口的布局与其相似。更多信息参见“类和接口”以及“文档注释”。
/*
* Copyright©2014-2015 www.10101111.com All Rights Reserved.
*/
package java.blah;
import java.blah.blahdy.BlahBlah;
/**
* Class description goes here.
*
* @author Firsname Lastname (account@10101111.com)
*/
public class Blah extends SomeClass {
/* A class implementation comment can go here. */
/** class Var1 documentation comment */
public static int classVar1;
/**
* classVar2 documentation comment that happen to be
* more than one line long
*/
private static Object classVar2;
/** instanceVar2 documentation comment */
public Object instanceVar1;
/** instanceVar3 documentation comment */
private Object[] instanceVar3;
/**
* …constructor Blah documentation comment…
*/
public Blah() {
//…implementation goes here…
}
/**
* …method doSomething documentation comment…
*/
public void doSomething() {
//…implementation goes here…
}
/**
* …method doSomethingElse documentation
* comment…
* @param someParam description
*/
public void doSomethingElse(Object someParam) {
//…implementation goes here…
}
}