本文是Google Java编程风格规范的完整定义。当且仅当一个Java源文件符合本文中的规范,我们才认为它符合Google Java编程风格。
和其他编程风格指南一样,这里不仅涉及编程格式的美观问题,还涉及其他类型的约定或编码标准。然而,本文主要关注的是我们普遍遵循的硬性规则,对于那些不是明确要求的,我们避免提供的意见(无论是人力还是工具)
在本文中,除非另有说明:
其他“术语说明”将偶尔出现在本文中。
本文中的示例代码是非标准的。也就是说,虽然示例代码是遵循Google编程风格,但并不意味着这是展现这些代码的唯一方式。示例中的格式选择不应该被强制定为规则。
源文件以其顶层的类名来命名,大小写敏感,文件扩展名为.java。
源文件以UTF-8编码
除了行结束符序列,ASCII水平空格字符(0x20)是源文件中唯一允许出现的空白字符,这意味着:
对于具有特殊转义序列的任何字符(\b, \t, \n, \f, \r, ", ’ 及 \),我们使用它的转义序列,而不是相应的八进制(比如\012)或Unicode(比如\u000a)转义。
对于剩余的非ASCII字符,是使用实际的Unicode字符(比如: ∞)还是使用等价的Unicode转义符(比如:\u221e),取决于哪个能让代码更易于阅读和理解,尽管在字符串和注释之外使用Unicode转义是非常不鼓励的。
提示:在使用Unicode转义符或是一些实际的Unicode字符时,建议做些注释给出解释,这有助于别人阅读和理解。
例子:
例子 | 讨论 |
---|---|
String unitAbbrev = “μs”; | 最好:即使没有注释也非常清晰 |
String unitAbbrev = “\u03bcs”; // “μs” | 允许:但是没有理由要这样做 |
String unitAbbrev = “\u03bcs”; // Greek letter mu, “s” | 允许:但是这样显得笨拙还容易出错 |
String unitAbbrev = “\u03bcs”; | 很差:读者根本看不出这是什么 |
return ‘\ufeff’ + content; // byte order mark | 很好:对于非打印字符,使用转义,并在必要时写上注释 |
提示:不要因为某些程序可能无法正确处理非ASCII字符而使您的代码变得不可读。当程序无法正确处理非ASSII字符时,它自然无法正确运行,你就会去fix这些问题了。
一个源文件包含,按顺序地:
以上每个部分之间用一个空行隔开。
如果一个文件包含许可证或版权信息,那么它应该被放在这里
package语句不换行。列限制(4.4 节,列限制:100)并不适用于package语句。
不应使用通配符import,不管是静态导入还是其他。
import语句不换行,列限制(4.4节,列限制:100)并不适用于import语句。
import导入的顺序如下:
如果同时存在静态导入与非静态导入,则以一个空白行分割。 import语句之间没有其他空白行。
每一个组中,导入的名称以ASCII排序显示。
静态导入不能用于静态嵌套类。
每个顶级类都再一个与它同名的源文件中。
例外:package-info.java,该文件中没有package-info类。
类的成员和初始化块顺序对易读性有很大的影响。但是没有一个统一正确的标准。不同的类可以以不同的方式对其内容进行排序。
重要的是,每个类都要按照一定的逻辑规律排序。维护者应该能解释这种排序逻辑。比如,新增的方法不能总是习惯性地添加到类的结尾,因为这样就是按时间顺序而非某种逻辑来排序的。
当一个类有多个构造函数,或是多个同名方法,这些函数/方法应该按照顺序出现在一起,中间不要放进其他代码。
术语说明:块状结构指的时一个类,方法或构造函数的主体。需要注意的是,数组初始化中的初始值可被选择性地视为块状结构(4.8.3.1 节)。
大括号用在if,else,for,do和while语句。甚至当它的实现为空或者只有一句话时,也要使用。
对于非空块和块状结构,大括号遵循Kernighan和Ritchie风格:
例子:
return () -> {
while (condition()) {
method();
}
};
return new MyClass() {
@Override public void method() {
if (condition()) {
try {
something();
} catch (ProblemException e) {
recover();
}
} else if (otherCondition()) {
somethingElse();
} else {
lastThing();
}
}
};
4.8.1节给出了枚举类的一些例外。
一个空块或者空的构造方法,可以在大括号开始之后直接结束大括号,中间不需要空格或换行。但是当一个由几个语句块联合组成的语句块时,则需要换行。(例如:if/else或try/catch/finally)
例子:
// This is acceptable
void doNothing() {}
// This is equally acceptable
void doNothingElse() {
}
// This is not acceptable: No concise empty blocks in a multi-block statement
try {
doSomething();
} catch (Exception e) {}
每当开始一个新的快,缩进就增加两个空格。当块结束时,缩进返回先前的缩进级别。缩进级别适用于代码和注释(见4.1.2节中的代码示例)
每个语句后要换行
Java代码单行限制长度为100个字符,一个字符是指任何Unicode编码点。除了以下情况,超出此上限的行必须进行换行,如4.5节所解释的
每个Unicode编码点都算作一个字符,即使其显示宽度大于或小于。例如,如果使用全宽度字符,您可以选择比此规则严格要求的更早地换行。
例外:
术语说明:一般情况下,一行长代码为了避免超出列限制而被分为多行,我们称之为自动换行。
这里并没有全面,确定性的准则来决定在每一种情况下如何自动换行。很多时候,对于同一段代码会有好几种有效的自动换行方式。
注:虽然换行的典型原因是为了避免溢出列限制,即使在事实上符合列限制的代码也可能由作者自行决定是否换行。
Tip: 提取方法或局部变量可以在不换行的情况下解决代码过长的问题
自动换行的基本准则是:更倾向于在更高的语法级别处断开。
MyLambda lambda =
(String label, Long value, Object obj) -> {
...
};
Predicate predicate = str ->
longExpressionInvolving(str);
注意:换行的主要目标是要有清晰的代码,而不是只包含最少行数的代码。
自动换行时,第一行后的每一行至少比第一行多缩进四个空格。
当存在连续自动换行时,缩进可能会多缩进不只4个空格。一般而言,两个连续行使用相同的缩进当前仅当它们开始于同级语法元素。
第4.6.3水平对齐一节中指出,不鼓励使用可变数目的空格来对齐前面的符号。
以下情况需要使用一个空行:
一个空行也可能出现在任何可以提高可读性的地方,例如在语句之间将代码组织成逻辑子部分。在第一个成员或初始化器之前或在类的最后一个成员或初始化器之后的空白行既不鼓励也不劝阻
多个连续的空行是允许的,但没有必要这样做(我们也不鼓励这样做)
除了语言或其他样式规则需要的地方之外,除了文字、注释和Javadoc用到单个空格之外,下面的地方也只显示一个ASCII空格。
这个规则并不要求或禁止一行的开始或结束处需要额外的空格,只对内部空格做要求。
术语说明:水平对齐指的是通过增加可变数量的空格来使某一行的字符与上一行的相应字符对齐。
这是允许的(而且在不少地方可以看到这样的代码),但Google编程风格对此不做要求。即使对于已经使用水平对齐的代码,我们也不需要去保持这种风格。
以下示例先展示未对齐的代码,然后是对齐的代码:
private int x; // this is fine
private Color color; // this too
private int x; // permitted, but future edits
private Color color; // may leave it unaligned
对齐可增加代码可读性,但它为日后的维护带来问题。考虑未来某个时候,我们需要修改一堆对齐的代码中的一行。这可能导致原本很漂亮的对齐代码变得错位。很可能它会提示你调整周围代码的空白来使一堆代码重新水平对齐(比如程序员想保持这种水平对齐的分格),这就会让你做许多的无用功,增加了reviewer的工作并且可能导致更多的合并冲突。
除非作者和reviewer都认为去掉小括号也不会使代码被误解,或是去掉小括号能让代码更易于阅读,否则我们不应该去掉小括号。我们没有理由假设读者能记住整个Java运算符优先级表。
枚举常量间用逗号隔开,换行可选。
private enum Answer {
YES {
@Override public String toString() {
return "yes";
}
},
NO,
MAYBE
}
没有方法和文档的枚举类可写成数组初始化的格式:
private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }
由于枚举类也是一个类,因此所有适用于其他类的格式规则也适用于枚举类。
不要使用组合声明,比如int a, b
例外:在循环头上,可以声明多个变量。
不要在一个代码块的开头把局部变量一次性都声明了,而是在第一次需要使用它时才声明。局部变量在声明时最好就进行初始化,或者声明后尽快进行初始化。
数组初始化可以写成块状结构,比如,下面的写法都是可以的:
new int[] { new int[] {
0, 1, 2, 3 0,
} 1,
2,
new int[] { 3,
0, 1, }
2, 3
} new int[]
{0, 1, 2, 3}
中括号是类型的一部分:String[] args,而非String args[].
术语说明:switch块的大括号内是一个或多个语句组。每个语句组包含一个或多个switch标签(case FOO: 或default:),后面跟着一条或多条语句(或者,最后一个语句组,后面跟着0条或多条语句)
和其它块状结构一致,switch块中的内容缩进为2个空格。
switch标签后,出现换行,缩进级别增加+2,就像打开了一个块。下面的switch标签返回到前一个缩进级别,就像一个块已经关闭一样
在一个switch块内,每个语句组要么通过break,continue,return或抛出异常来终止,要么通过一条注释来说明程序将继续执行到下一个语句组,任何能表达这个意思的注释都是OK的(典型的用 // fall through)。这个特殊的注释并不需要在最后一个语句组(一般是default)中出现。示例:
switch (input) {
case 1:
case 2:
prepareOneOrTwo();
// fall through
case 3:
handleOneTwoOrThree();
break;
default:
handleLargeNumber(input);
}
注意,在case1: 之后不需要注释,只在语句组的末尾加上注释。
每个switch语句都包含一个default语句组,即使它什么代码也不包含。
例外:如果枚举类型的switch语句包含该类型的所有可能值的显式用例,那么它可能会省略默认语句组。这使得ide或其他静态分析工具在遗漏任何案例时能够发出警告
注解紧跟在文档块后面,应用于类、方法和构造函数,一个注解独占一行。这些换行不属于自动换行(第4.5节,自动换行),因此缩进级别不变。例如:
@Override
@Nullable
public String getNameIfPresent() { ... }
例外:单个的注解可以和签名的第一行出现在同一行。例如:
@Override public int hashCode() { ... }
应用于字段的注解紧随文档块出现,应用于字段的多个注解允许与字段出现在同一行。例如:
@Partial @Mock DataLoader loader;
参数和局部变量注解没有特定规则。
本节讨论实现注释。Javadoc在第7节Javadoc中单独讲解。
任何换行符之前可以有任意空格,然后是实现注释。这样的注释使该行非空白。
块注释与其周围的代码在同一缩进级别。它们可以是/*…*/风格,也可以是//…风格。对于多行的/*…*/注释,后续行必须从*开始,并且与前一行的*对齐。以下示例注释都是可以的。
/*
* This is // And so /* Or you can
* okay. // is this. * even do this. */
*/
注释不要封闭在由星号或其它字符绘制的框架里。
Tip: 在写多行注释时,如果您想要自动的代码格式器在必要时重新换行(即注释像段落风格一样),那么请使用/…/。大多数格式化程序不会使// …风格的注释块重新换行。
类和成员的修饰符,如果存在,则按Java语言规范中推荐的顺序出现。
public protected private abstract default static final transient volatile synchronized native strictfp
long整型的字面值使用大写L作为后缀,从不小写(以避免与数字1混淆)。例如,3000000000L 而不是 3000000000l
标识符只能使用ASCII字母和数字,因此每个有效的标识符名称都能匹配正则表达式\w+.
在Google其它编程风格中使用的特殊前缀或后缀,如name_ mName, s_name和kName,在Java编程风格中都不再使用。
包名全部小写,连续的单词只是简单地连接起来,不使用下划线。例如,com.example.deepspace,而不是 com.example.deepSpace 或者 com.example.deep_space
类名都以UpperCamelCase风格编写。
类名通常是名词或名词短语,例如,Character 或 ImmutableList。接口名称有时可能是名词或名词短语(例如:List),有时又是形容词或形容词短语(例如Readable)。
现在还没有特定的规则或行之有效的约定来命名注解类型。
测试类的命名以它要测试的类的名称开始,以Test结束。例如,HashTest或HashIntegrationTest。
方法名都以lowerCamelCase风格编写。
方法名通常是动词或动词短语。例如,sengMessage或stop。
下划线可能出现在JUnit测试方法名称中用以分割名称的逻辑组件,每个组件使用lowerCamelCase编写。一个典型的模式时:_,例如testPop_emptyStack。并不存在唯一正确的方式来命名测试方法。
常量名命名模式为CONSTANT_CASE:全部字母大写,用下划线分割单词。那到底什么算是一个常量?
每个常量都有一个静态final字段,其内容是不可变的,并且其方法没有可检测的副作用。这包括基元,字符串,不可变类型和不可变类型的不可变集合。如果任何实例的observable状态可以改变,它不是一个常量。只是永远不打算改变对象一般是不够的,它要真的一直不变才能将它视为常量。
// Constants
static final int NUMBER = 5;
static final ImmutableList NAMES = ImmutableList.of("Ed", "Ann");
static final ImmutableMap AGES = ImmutableMap.of("Ed", 35, "Ann", 32);
static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }
// Not constants
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final Set mutableCollection = new HashSet();
static final ImmutableSet mutableElements = ImmutableSet.of(mutable);
static final ImmutableMap mutableValues =
ImmutableMap.of("Ed", mutableInstance, "Ann", mutableInstance2);
static final Logger logger = Logger.getLogger(MyClass.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};
这些名字通常是名词或名词短语。
非常量字段以lowerCamelCase风格编写
这些名字通常是名词或者名词短语。例如,computedValues 或 index
参数名以lowerCamelCase风格编写。
在公共方法中参数应该避免使用单个字符名明
局部变量名以lowerCamelCase风格编写。
即使局部变量是final和不可变的,也不应该把它视为常量,自然也不能用常量的规则去命名它。
类型变量可用以下两种风格之一进行命名:
有时,我们有不只一种合理的方式将一个英语词组转换成驼峰形式。如缩略语或不寻常的结构(例如"IPv6"或"iOS")。Google指定了以下的转换方案。
名字从散文形式开始:
注意,原词的大小写几乎被完全忽略了。例如:
Prose form | Correct | Incorrect |
---|---|---|
“XML HTTP request” | XmlHttpRequest | XMLHTTPRequest |
“new customer ID” | newCustomerId | newCustomerID |
“inner stopwatch” | innerStopwatch | innerStopWatch |
“supports IPv6 on iOS?” | supportsIpv6OnIos | supportsIPv6OnIOS |
“YouTube importer” | YouTubeImporter YoutubeImporter* |
*可接受,但不推荐
注意:在英语中,某些带有连字符的单词形式不唯一:例如"nonempty"和"non-emtpy"都是正确的,所以方法名称checkNonempty和chechNonEmpty也是正确的。
只要是合法的,就把@Override注解给用上。这包括重写超类方法的类方法,实现接口方法的类方法和重定义超接口方法的接口方法。
例外:当父方法未@Deprecated时,可以省略@Override
除了下面的例子,对捕获的异常不做响应是极少正确的。(典型的响应方式是打印日志,或者如果它被认为是不可能的,则把它当做一个AssertionError重新抛出。)
如果它确实是不需要在catch块中做任何响应,需要做注释加以说明(如下面的例子)
try {
int i = Integer.parseInt(response);
return handleNumericResponse(i);
} catch (NumberFormatException ok) {
// it's not numeric; that's fine, just continue
}
return handleTextResponse(response);
例外:在测试中,如果一个捕获的异常被命名为expected,则它可以被不加注释地忽略。下面是一种常见的情形,用以确保所测试的方法会抛出一个期望中的异常,因此在这里就没有必要加注释。
try {
emptyStack.pop();
fail();
} catch (NoSuchElementException expected) {
}
使用类名调用类的静态成员,而不是具体某个对象或表达式
Foo aFoo = ...;
Foo.aStaticMethod(); // good
aFoo.aStaticMethod(); // bad
somethingThatYieldsAFoo().aStaticMethod(); // very bad
极少会去重载Object.finalize
Tip: 不要使用finalize。如果 你非要使用它,请先仔细阅读和理解Effective Java 第7条款:“Avoid Finalizers”,然后不要使用它。
Javadoc块的基本格式如下所示:
/**
* Multiple lines of Javadoc text are written here,
* wrapped normally...
*/
public int method(String p1) { ... }
或者是以下单行形式:
/** An especially short bit of Javadoc. */
基本格式总是可以接受的。当整个Javadoc块能容纳于一行时(且没有Javdoc标记@XXX),可以使用单行形式。
空行会出现在段落之间和和Javadoc标记之前(如果有的话)。除了第一个段落,每个段落第一个单词前都有标签
,并且它和第一个单词间没有空格。
标准的块标记按以下顺序出现:@param, @return, @throws, @deprecated,前面这4种标记如果出现,描述都不能为空。当描述无法再一行中容纳,连续行需要至少缩进4个空格。
每个Javadoc块以一个简短的摘要片段开始。这个片段是非常重要的,在某些情况下,它是唯一出现的文本,比如在类和方法索引中。
这是一个小片段,可以是一个名字短语或动词短语,但不是一个完整的句子。它不会以A {@code Foo} is a …或This method returns…开头,它也不会是一个完整的祈使句,如Save the record…。然而,由于开头大写及被加了标点,它看起来就像是个完整的句子。
Tip: 一个常见的错误是把简单的Javadoc写成/** @return the customer ID /,这是不正确的,它应该写成/* Returns the customer ID. */。
至少在每个public类及它的每个public和protected成员处使用Javadoc,以下是一些列外:
还可能存在其他Javadoc内容,如第7.3.4节"非必需Javadoc"中所述。
对于简单明显的方法如getFoo,Javadoc是可选的。在这种情况下除了写"Returns the foo",确实也没什么值得写了。
重要:如果有一些相关信息是需要读者了解的,那么以上的例外不应作为忽视这些信息的理由。例如,对于方法名getCanonicalName,就不应该忽视文档说明,因为读者很可能不知道词语canonical name指的是什么。
如果一个方法重载了超类中的方法,那么Javadoc并非必须的。
其他类和成员根据需要和期望使用Javadoc
每当实现注释用于定义类或成员的总体目的或行为时,该注释将改为Javadoc。
非必需的Javadoc不是严格要求遵守第7.1.2,7.1.3和7.2节的格式化规则,但当然是建议。