4. 格式

术语说明 :块状结构指的是类、方法或构造器的主体。注意,根据4.8.3.1节关于 数组初始化器 的内容,任何数组初始化器都可以选择性地被视为块状结构。

4.1. 花括号

4.1.1. 选择性的花括号的使用

ifelsefordowhile 语句中,即使主体为空或只包含一个语句,也应该写出花括号。

其他选择性的花括号,比如lambda表达式中的花括号,依然不是必须写出的。

4.1.2. 非空块:K & R 风格

非空块和块状结构中的花括号遵循Kernighan & Ritchie风格(“ 埃及括号 ”):

  • 在打开花括号之前不换行,除非下面有详细说明。

  • 在打开花括号之后换行。

  • 在关闭花括号之前换行。

  • 仅在该花括号终止一个语句或终止方法、构造器或命名类的主体时,在关闭花括号之后才需要换行。例如,如果花括号后面跟的是 else 或逗号,则不换行。

例外:在某些场景下,尽管规则允许你简单地使用一个以分号( ; )结束的语句,但你仍可以在此选择使用一个语句块。在这种情况下,这个语句块开头的花括号前会有一个换行。这种特殊的语句块通常用于限定局部变量的作用范围,例如在switch语句中。

例子:

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();
        }
        {
            int x = foo();
            frob(x);
        }
    }
};

关于枚举类的一些例外情况,请参见第4.8.1节, 枚举类

4.1.3. 空代码块:应简洁表示

一个空的代码块或类似块的结构可以按照K & R风格(如第4.1.2节 非空块:K & R风格 所描述)。或者,你也可以在打开后立即关闭它,中间没有字符也不需要换行(即 {} ), 除非 它是多块语句的一部分(直接包含多个块的语句,例如 if / elsetry / catch / finally )。

例如:

// 这是可以的
void doNothing() {}

// 这同样也是可以的
void doNothingElse() {
}
// 不可以这样:多块语句中不能出现打开即关闭的简略花括号
try {
    doSomething();
} catch (Exception e) {}

4.2. 代码块缩进:增加2个空格

每次打开一个新的代码块或块状结构时,缩进增加两个空格。当块结束时,缩进返回到之前的缩进级别。这种缩进级别同时适用于块中的代码以及注释。(参见第4.1.2节中的示例, 非空块:K & R风格 。)

4.3. 一行一个语句

每个语句后都应换行。

4.4. 列限制:100

Java代码的列限制为100个字符。这里的“字符”指的是任何Unicode码位。除非以下特别说明,任何超出此限制的行都必须换行,如第4.5节 换行 中所解释的。

Tip

每个Unicode码位都算作一个字符,即使其显示宽度大于或小于一个字符。例如,如果使用 全角字符 ,你可能会选择在此规则严格要求的位置之前就换行。

例外:

  • 在某些情况下,遵循列限制是不可能的(例如,Javadoc中的长URL,或者一个长的JSNI方法引用)。

  • 包声明和导入语句(参见第3.2节 包声明 和第3.3节 导入语句 )。

  • 注释中可能会被复制并粘贴到shell的中命令行。

  • 在极少数情况下需要使用的非常长的标识符是允许超过列限制的。在这种情况下,周围代码的换行方式应遵循 google-java-format

4.5. 换行

术语说明: 将可以在一行内书写的代码分成多行的操作被称为换行。

没有全面且确定的公式明确指出在所有情况下如何进换行。很多时候,同一段代码有多种正确的换行方式。

Tip

注意: 尽管进换行的典型原因是为了避免超过列限制,但实际上在列限制内的代码也可以根据作者的判断进换行。

Tip

提示: 有时重构一个方法或定义一个局部变量可以解决超过列限制的问题,而不需要换行。

4.5.1. 换行的位置

换行的首要准则是:倾向于在 更高级的句法层次 进换行。此外:

    1. 当一行在非赋值运算符处断开时,换行的位置位于符号之前。(注意,这与Google为其他语言所采用的风格不同,如C++和JavaScript。)

    • 这同样适用于以下”类运算符”的符号:

      • 点分隔符( .

      • 方法引用的两个冒号( ::

      • 类型约束中的与符号( <T extends Foo & Bar>

      • catch块中的管道符号( catch (FooException | BarException e)

    1. 当一行在赋值运算符处断开时,换行位置通常位于符号之后,但两种方式都是可以接受的。

    • 这也适用于增强的 for 循环(”foreach”)语句中的”类赋值运算符”————冒号。

    1. 方法或构造函数的名称应紧挨着其后的左括号( ( )。

    1. 逗号( , )应紧挨着它前面的标记。

    1. lambda箭头旁绝不换行,但是如果lambda的主体仅由单个未括起来的表达式组成,那么可以紧跟在箭头后面换行。示例:

    MyLambda<String, Long, Object> lambda =
        (String label, Long value, Object obj) -> {
            ...
        };
    
    Predicate<String> predicate = str ->
        longExpressionInvolving(str);
    

Tip

注意: 换行的主要目的是使代码更清晰,其不一定是行数最少的。

4.5.2. 行缩进至少4个空格

换行时,第一行之后的每一行(每一个连续的行)至少从原行缩进4个空格。

当有多个连续行时,根据需要,缩进可以在4个空格之外变化。通常,只有当两个连续行从语法上开始于平行的元素时,它们才使用相同的缩进级别。

第4.6.3节关于 水平对齐 讨论了一种不鼓励使用的做法————用数量变化的空格来使某些标记与前面的行对齐。

4.6. 空白字符

4.6.1. 垂直空白

单个空白行始终应出现在:

    1. 一个类的连续成员或初始化器之间:字段、构造函数、方法、嵌套类、静态初始化器和实例初始化器。

    • 例外: 两个连续字段之间(它们之间没有其他代码)的空白行是可选的。这样的空行一般根据需要用于创建字段的逻辑分组。

    • 例外: 枚举常量之间的空白行在 第4.8.1节 中有描述。

    1. 此文档的其他部分所要求的(例如第3节, 源文件结构 ,和第3.3节 导入语句 )。

单个空白行也可以出现在任何使用它可以提高代码可读性的位置,例如在语句之间以将代码组织成逻辑子部分。类的第一个成员或初始化器之前,或者最后一个成员或初始化器之后的空白行既不被鼓励也不被反对。

多个连续的空白行是允许的,但从不被要求(或鼓励)。

4.6.2. 水平空白

除了语言或其他风格规则所要求的地方,以及字符串文本、注释和Javadoc之外,单个ASCII空格字符也 出现在以下位置。

    1. 将任何保留字,如 ifforcatch ,与其后面的左括号( ( )隔开

    1. 将任何保留字,如 elsecatch ,与其前面的右花括号( } )隔开

    1. 在任何左花括号( { )之前,但有两个例外:

    • @SomeAnnotation({a, b}) (无空格)

    • String[][] x = {{"foo"}}; (根据下面的第9项, {{ 之间不需要空格)

    1. 在任何二元或三元运算符的两侧。这也适用于以下的”类运算符”的符号:

    • 并行类型约束中的与符号: <T extends Foo & Bar>

    • 处理多个异常的catch块中的管道符号: catch (FooException | BarException e)

    • 增强的 for (”foreach”)语句中的冒号( :

    • lambda表达式中的箭头: (String str) -> str.length()

    但不包括:

    • 方法引用中的两个冒号( :: ),正确的写法应类似 Object::toString

    • 点分隔符( . ),正确的写法应类似 object.toString()

    1. ,:; 或类型转换的右括号 ) 之后

    1. 在代码中任何内容和开始注释的双斜杠 // 之间。允许多个空格。

    1. 在开始注释的双斜杠 // 和注释内容之间。允许多个空格。

    1. 在声明的类型和变量名之间: List<String> list

    1. 在数组初始化器的两个花括号内部(可选)

    • new int[] {5, 6} and new int[] { 5, 6 } 都是可行的

    1. 在类型注解和 []... 之间

此规则不应被解读为在行的开始或结束时要求或禁止额外的空格;它只涉及内部空格。

4.6.3. 水平对齐:永远不是必要的

术语说明: 水平对齐是指在代码中添加变化数量的额外空格,目的是使某些标记直接出现在前面几行的某些其他标记的下方

这种做法是允许的,但Google风格永远不要求它。即使在已经使用了水平对齐的地方,也不要求保持水平对齐。

以下是一个不使用对齐的例子,然后是一个使用对齐的例子:

private int x;      // 这样很好
private Color color; // 这也是

private int   x;      // 这是允许的,但未来的编辑
private Color color;  // 可能会使它不再对齐

Tip

提示: 对齐确实可以帮助提高可读性,但它为未来的维护带来了问题。考虑一个将来需要触碰某一行的更改,该更改可能会使之前令人满意的格式变得混乱(当然这种混乱是允许的)。更常见的是,它会促使编码者(也许是你)也调整附近行的空白,可能会触发一系列新的格式化。如此一来,那一行的更改现在就有了一个“爆炸半径”。在最坏的情况下,这可能导致毫无意义的繁忙工作,但即使在最好的情况下,它仍然会破坏版本历史信息,减慢审查者的速度,并加剧合并冲突(merge conflicts)。

4.7. 分组括号:推荐使用

对于非必须的分组括号,只有当作者和审查者都认为代码在没有它们的情况下不可能被误解,且它们不会使代码更易于阅读时,才能省略它们。假设每个读者都记住了整个Java运算符优先级表是不合理的。

4.8. 具体结构

4.8.1. 枚举类

在枚举常量后面的每个逗号后,换行是可选的。也允许额外的空白行(通常只有一行)。以下是一种可能的写法:

private enum Answer {
    YES {
        @Override public String toString() {
            return "yes";
        }
    },

    NO,
    MAYBE
}

一个没有方法且其常量上没有文档注释的枚举类可以选择按照数组初始化器的格式进行编写(参见4.8.3.1节有关 数组初始化器 的内容)。

private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }

由于枚举类是类,因此编写类的所有其他格式规则都适用于它。

4.8.2. 变量声明

4.8.2.1. 一次只声明一个变量

每个变量声明(字段或局部变量)只声明一个变量:不能使用 int a, b; 这样的声明。

例外: for 循环的头部中,多个变量的声明是可以接受的。

4.8.2.2. 按需声明

局部变量一般不在其所在的块或块状结构的开始处声明。相反,局部变量在首次使用的地方附近(合理范围内)声明,以最小化它们的作用域。局部变量声明时通常会设定其初始值,或在声明后立即进行初始化。

4.8.3. 数组

4.8.3.1. 数组初始化器:可以是“块状”的

任何数组初始化器都可以选择按照“块状结构”的格式进行编写。例如,以下写法都是可以接受的(这里 并未 列出所有可行的写法):

new int[] {
    0, 1, 2, 3
}

new int[] {
    0, 1,
    2, 3
}

new int[] {
    0,
    1,
    2,
    3
}

new int[]
    {0, 1, 2, 3}

4.8.3.2. 不要使用C语言风格的声明

方括号是类型的一部分,而非变量的一部分:正确的写法应该为 String[] args ,而不是 String args[]

4.8.4. switch语句

术语说明: 在switch语句块的花括号内有一个或多个语句组。每个语句组由一个或多个switch标签(要么是 case FOO: 要么是 default: )组成,后面跟着一个或多个语句(对于最后一个语句组,可能是零个或多个语句)。

4.8.4.1. 缩进

与任何其他块一样,switch块中内容的缩进为2格。

在switch标签后应有一个换行,且缩进级别增加2格,就好像一个块正在被打开一样。接下来的switch标签回到了之前的缩进级别,就好像一个块已经被关闭了一样。

4.8.4.2. 贯穿:需要注释

在switch块内,每个语句组要么突然终止(使用 breakcontinuereturn 或抛出异常),要么用注释标记,以指示执行会或可能继续进入下一个语句组。任何传达贯穿意思的注释都是足够的(通常是 // fall through )。在switch块的最后一个语句组中,不需要这个特殊注释。示例如下

switch (input) {
    case 1:
    case 2:
        prepareOneOrTwo();
        // fall through
    case 3:
        handleOneTwoOrThree();
        break;
    default:
        handleLargeNumber(input);
}

注意单个的switch标签 case 1 后面贯穿不需要做任何注释,只有在语句组的最后贯穿才需要注释。

4.8.4.3. 必须要有 default 标签

每个switch语句都包括一个default语句组,即使其中不包含任何代码。

例外: 对于 enum 类的switch语句,如果它明确地包括覆盖该类型的所有可能值的情况,则可以省略 default 语句组。如果遗漏了任何可能值,这样做可以让IDE或其他静态分析工具能够发出警告。

4.8.5. 注解

4.8.5.1. 类型注解

应用于类型的注解直接出现在被注解的类型之前。如果注解是用 @Target(ElementType.TYPE_USE) 进行元注解的,那么它就是一个类型注解。示例如下:

final @Nullable String name;

public @Nullable Person getPersonByName(String name)

4.8.5.2. 类注解

应用于类的注解紧跟在文档块之后,并且每个注解都列在自己的行上(也就是每行一个注解)。这些换行并不构成换行(见4.5节, 换行),所以缩进级别不增加。示例如下:

@Deprecated
@CheckReturnValue
public final class Frozzler { ... }

4.8.5.3. 方法/构造函数注解

应用于方法或构造函数的注解的规则和 上一节 一样。实例如下:

@Deprecated
@Override
public String getNameIfPresent() { ... }

4.8.5.4. 字段注解

应用于字段的注解也紧跟在文档块之后,但在这种情况下,多个注解(可能带参数)可以列在同一行上;例如:

@Partial @Mock DataLoader loader;

4.8.5.5. 参数/局部变量注解

应用于参数或局部变量的注解没有一个具体的编写规则(当然,当注解是类型注解时除外)。

4.8.6. 注释

本节讨论注释的实现。关于Javadoc的内容在第7节 Javadoc 中单独讨论。

尽管一行可能只包含注释和空白字符,但由于有注释的存在,它并不被视为一个完全的空白行。

4.8.6.1. 块状注释

块状注释与周围的代码具有相同的缩进级别。它们可以是 /* ... */ 或者 // ... 形式的。对于多行的 /* ... */ 注释,后续的行必须以 * 开始,并且这个 * 要与上一行的 * 对齐。

/*
 * This is          // And so           /* Or you can
 * okay.            // is this.          * even do this. */
 */

注释不应被星号或其他字符所构成的框框起来。

Tip

提示: 当编写多行注释时,如果你希望在必要时,自动的代码格式化工具能重新调整行的格式(段落样式),你应该使用 /* ... */ 形式。大多数格式化工具不会重新调整 // ... 形式的注释块中的行。

4.8.7. 修饰符

当存在类和成员的修饰符时,它们出现的顺序应遵循Java语言规范推荐的顺序:

public protected private abstract default static final transient volatile synchronized native strictfp

4.8.8. 数值字面量

长整型( long 类型)字面量使用大写的 L 作为后缀,而绝不能使用小写的(以避免与数字 1 混淆)。例如,应写为 3000000000L 而非 3000000000l