(译)JavaPoet 官方教程

前言

JavaPoet 是一个用于生成 .Java 源文件的 Java API 。在执行注解处理或与元数据文件(例如,数据库模式、协议格式)交互等操作时,源文件生成可能很有用。通过生成代码,你可以消除样板代码,同时为元数据保留一个真实来源。

例子

这是一个(无聊的) HelloWorld 类:

package com.example.helloworld;

public final class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, JavaPoet!");
    }
}
复制代码

然后这是由 JavaPoet 生成的(令人激动的)代码:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    .returns(void.class)
    .addParameter(String[].class, "args")
    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(main)
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);
复制代码

为了声明 main 方法,我们创建了一个 MethodSpec “main” ,它配置了修饰符、返回类型、参数和代码语句。我们将 main 方法添加到 HelloWorld 类中,然后将其添加到 HelloWorld.java 文件中。

在本例中,我们将文件写入 System.out ,但我们也可以将其作为字符串(JavaFile.toString())获取,或者将其写入文件系统(JavaFile.writeTo())。

代码与控制流程

JavaPoet 的大多数 API 都使用普通、不可变的 Java 对象,也提供了 Builder 模式、链式方法和可变参数来使 API 更加友好。JavaPoet 提供了类和接口(Typespec)、字段(FieldSpec)、方法和构造函数(MethodSpec)、参数(ParameterSpec)和注解(AnnotationSpec)等模型。

但是方法和构造函数的主体没有建模。没有表达式类,没有语句类,也没有语法树节点。相反,JavaPoet 对代码块使用字符串:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addCode(""
        + "int total = 0;\n"
        + "for (int i = 0; i < 10; i++) {\n"
        + "    total += i;\n"
        + "}\n")
    .build();
复制代码

以上将生成如下代码:

void main() {
    int total = 0;
    for (int i = 0; i < 10; i++) {
        total += i;
    }
}
复制代码

手动写的分号、换行和缩进都很繁琐,因此 JavaPoet 提供了 API 来简化这些操作。addStatement() 负责分号和换行,而 beginControlFlow() + endControlFlow() 用于大括号、换行和缩进:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("int total = 0")
    .beginControlFlow("for (int i = 0; i < 10; i++)")
    .addStatement("total += i")
    .endControlFlow()
    .build();
复制代码

这个例子很蹩脚,因为生成的代码是常量!假设不只是将 0 自增到 10,而是要使操作和范围可配置,下面是一个生成方法的方式:

private MethodSpec computeRange(String name, int from, int to, String op) {
    return MethodSpec.methodBuilder(name)
        .returns(int.class)
        .addStatement("int result = 1")
        .beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")
        .addStatement("result = result " + op + " i")
        .endControlFlow()
        .addStatement("return result")
        .build();
}
复制代码

下面是我们调用 computeRange("multiply10to20", 10,20, "*") 时得到的结果:

int multiply10to20() {
    int result = 1;
    for (int i = 10; i < 20; i++) {
      result = result * i;
    }
    return result;
}
复制代码

方法生成方法!由于 JavaPoet 生成的是源代码而不是字节码,所以您可以阅读检查它以确保它是正确的。

一些控制流语句,例如 if/else ,可以具有无限的控制流可能性。你可以使用 nextControlFlow() 来处理这些选项:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("long now = $T.currentTimeMillis()", System.class)
    .beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
    .nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time stood still!")
    .nextControlFlow("else")
    .addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
    .endControlFlow()
    .build();
复制代码

以上代码将生成:

void main() {
    long now = System.currentTimeMillis();
    if (System.currentTimeMillis() < now)  {
        System.out.println("Time travelling, woo hoo!");
    } else if (System.currentTimeMillis() == now) {
        System.out.println("Time stood still!");
    } else {
        System.out.println("Ok, time still moving forward");
    }
}
复制代码

使用 try/catch 捕获异常也是 nextControlFlow() 的一个用例:

MethodSpec main = MethodSpec.methodBuilder("main")
    .beginControlFlow("try")
    .addStatement("throw new Exception($S)", "Failed")
    .nextControlFlow("catch ($T e)", Exception.class)
    .addStatement("throw new $T(e)", RuntimeException.class)
    .endControlFlow()
    .build();
复制代码

将生成:

void main() {
    try {
        throw new Exception("Failed");
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
复制代码

$L -> 输出字面量

调用 beginControlFlow()addStatement 中的字符串连接会分散注意力,太多的运算符,为了解决这个问题,JavaPoet 提供了一种与 String.format() 类似但不兼容的语法。它接收 $L 并在输出中发出一个 字面量 。这就像 Formatter%s

private MethodSpec computeRange(String name, int from, int to, String op) {
    return MethodSpec.methodBuilder(name)
        .returns(int.class)
        .addStatement("int result = 0")
        .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
        .addStatement("result = result $L i", op)
        .endControlFlow()
        .addStatement("return result")
        .build();
}
复制代码

字面量 直接发送到输出代码,没有转义。文字的参数可以是字符串、基本数据类型和下面将描述的一些 JavaPoet 类型。

$S -> 输出字符串

在发出包含字符串文本的代码时,我们可以使用 $S 来发出 字符串 ,并使用引号和转义。下面代码输出 3 个方法,每个方法返回自己的名字:

public static void main(String[] args) throws Exception {
    TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
        .addMethod(whatsMyName("slimShady"))
        .addMethod(whatsMyName("eminem"))
        .addMethod(whatsMyName("marshallMathers"))
        .build();

    JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
        .build();

    javaFile.writeTo(System.out);
}

private static MethodSpec whatsMyName(String name) {
    return MethodSpec.methodBuilder(name)
        .returns(String.class)
        .addStatement("return $S", name)
        .build();
}
复制代码

在这种情况下,使用 $S 给我们打上引号:

public final class HelloWorld {
    String slimShady() {
        return "slimShady";
    }

    String eminem() {
        return "eminem";
    }

    String marshallMathers() {
        return "marshallMathers";
    }
}
复制代码

$T -> 输出类型

我们 Java 程序员喜欢我们的类型:它们使我们的代码更容易理解。JavaPoet 也加入了。它对类型有丰富的内置支持,包括自动生成 import 语句。只需使用 $T 来引用 类型

MethodSpec today = MethodSpec.methodBuilder("today")
    .returns(Date.class)
    .addStatement("return new $T()", Date.class)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(today)
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);
复制代码

将生成以下 .java 文件,并完成必要的 import

package com.example.helloworld;

import java.util.Date;

public final class HelloWorld {
    Date today() {
        return new Date();
    }
}
复制代码

我们传递 Date.class 来引用一个恰好在生成代码时可用的类。其实并不需要这样,下面是一个类似的例子,但是这个例子引用了一个不存在的类:

ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");

MethodSpec today = MethodSpec.methodBuilder("tomorrow")
    .returns(hoverboard)
    .addStatement("return new $T()", hoverboard)
    .build();
复制代码

这个还不存在的类也被 import :

package com.example.helloworld;

import com.mattel.Hoverboard;

public final class HelloWorld {
    Hoverboard tomorrow() {
        return new Hoverboard();
    }
}
复制代码

ClassName 类型非常重要,在使用 JavaPoet 时经常需要它。它可以识别任何已声明的类。声明类型只是 Java 丰富类型系统的开始:我们还有数组、参数化类型、通配符类型和类型变量。JavaPoet 有用于构建每个类的类:

ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("return result")
    .build();
复制代码

JavaPoet 将分解每个类型,并在可能的地方 import 其组件。

package com.example.helloworld;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

public final class HelloWorld {
    List<Hoverboard> beyond() {
        List<Hoverboard> result = new ArrayList<>();
        result.add(new Hoverboard());
        result.add(new Hoverboard());
        result.add(new Hoverboard());
        return result;
    }
}
复制代码

$N -> 以名称引用生成的另一个声明

生成的代码通常是自引用的,使用 $N 以其名称引用另一个生成的声明。下面是一个方法中调用另一个方法:

public String byteToHex(int b) {
    char[] result = new char[2];
    result[0] = hexDigit((b >>> 4) & 0xf);
    result[1] = hexDigit(b & 0xf);
    return new String(result);
}

public char hexDigit(int i) {
    return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
复制代码

在生成上面的代码时,我们使用 $NhexDigit() 方法作为参数传递给 byteToHex() 方法:

MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
    .addParameter(int.class, "i")
    .returns(char.class)
    .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
    .build();

MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
    .addParameter(int.class, "b")
    .returns(String.class)
    .addStatement("char[] result = new char[2]")
    .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
    .addStatement("result[1] = $N(b & 0xf)", hexDigit)
    .addStatement("return new String(result)")
    .build();
复制代码

静态导入

JavaPoet 支持静态导入。它通过显式地收集类型成员名来实现这一点。让我们使用静态导入来增强前面的例子:

...
ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add($T.createNimbus(2000))", hoverboard)
    .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
    .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
    .addStatement("$T.sort(result)", Collections.class)
    .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
    .build();

TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
    .addMethod(beyond)
    .build();

JavaFile.builder("com.example.helloworld", hello)
    .addStaticImport(hoverboard, "createNimbus")
    .addStaticImport(namedBoards, "*")
    .addStaticImport(Collections.class, "*")
    .build();
复制代码

JavaPoet 将首先按照配置将您的静态导入块添加到文件中,匹配并处理所有调用,并根据需要导入所有其他类型。

package com.example.helloworld;

import static com.mattel.Hoverboard.Boards.*;
import static com.mattel.Hoverboard.createNimbus;
import static java.util.Collections.*;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

class HelloWorld {
    List<Hoverboard> beyond() {
        List<Hoverboard> result = new ArrayList<>();
        result.add(createNimbus(2000));
        result.add(createNimbus("2001"));
        result.add(createNimbus(THUNDERBOLT));
        sort(result);
       return result.isEmpty() ? emptyList() : result;
    }
}
复制代码

代码块格式字符串

代码块可以通过几种方式指定占位符的值,代码块上的每个操作只能使用一种样式。

相对参数

将格式字符串中每个占位符的参数值传递给 CodeBlock.add() 。在每个例子中,我们生成代码说“我吃了3个玉米卷”

CodeBlock.builder().add("I ate $L $L", 3, "tacos")
复制代码

位置参数

在格式字符串的占位符前放置一个整数索引(基于1),以指定使用哪个参数。

CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)
复制代码

命名参数

使用 $argumentName:X 语法,其中 X 是格式字符,使用格式字符串中包含所有参数键的映射调用 CodeBlock.addNamed() 。参数名使用 a-zA-Z0-9_ 中的字符,并且必须以小写字符开头。

Map<String, Object> map = new LinkedHashMap<>();
map.put("food", "tacos");
map.put("count", 3);
CodeBlock.builder().addNamed("I ate $count:L $food:L", map)
复制代码

方法

上面所有的方法都有一个代码体。使用 Modifiers.ABSTRACT 得到一个没有任何主体的方法。只有当所包含的类是抽象类或接口类时,这才是合法的。

MethodSpec flux = MethodSpec.methodBuilder("flux")
    .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addMethod(flux)
    .build();
复制代码

将生成如下代码:

public abstract class HelloWorld {
    protected abstract void flux();
}
复制代码

其他修饰词在允许的地方起作用。注意,在指定修饰符时,JavaPoet 使用 javax.lang.model.element.Modifier,一个在 Android 上不可用的类。此限制仅适用于代码生成代码,输出代码可以在任何地方运行:JVM 、Android 和 GWT。

方法还有参数、异常、变量变量、Javadoc、注解、类型变量和返回类型。所有这些都配置了 MethodSpec.Builder

构造函数

MethodSpec 是一个有点用词不当的词,它也可以用于构造函数:

MethodSpec flux = MethodSpec.constructorBuilder()
    .addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "greeting")
    .addStatement("this.$N = $N", "greeting", "greeting")
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
    .addMethod(flux)
    .build();
复制代码

将生成:

public class HelloWorld {
    private final String greeting;

    public HelloWorld(String greeting) {
        this.greeting = greeting;
    }
}
复制代码

在大多数情况下,构造函数就像方法一样工作。在发出代码时,JavaPoet 会把构造函数放在输出文件中的方法之前。

参数

使用 ParameterSpec.builder()MethodSpecaddParameter() 在方法和构造函数上声明参数:

ParameterSpec android = ParameterSpec.builder(String.class, "android")
    .addModifiers(Modifier.FINAL)
    .build();

MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
    .addParameter(android)
    .addParameter(String.class, "robot", Modifier.FINAL)
    .build();
复制代码

虽然上面生成 androidrobot 参数的代码不同,但是输出是一样的:

void welcomeOverlords(final String android, final String robot) {
}
复制代码

字段

与参数一样,可以使用 Builder 或使用方便的辅助方法创建字段:

FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(android)
    .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
    .build();
复制代码

将生成:

public class HelloWorld {
    private final String android;

    private final String robot;
}
复制代码

当字段具有 Javadoc 、注解或字段初始化器时,扩展 Builder 是必要的。字段初始化器使用与上面代码块相同的 String.format() 类语法:

FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .initializer("$S + $L", "Lollipop v.", 5.0d)
    .build();
复制代码

将生成:

private final String android = "Lollipop v." + 5.0;
复制代码

接口

JavaPoet 生成接口也是没有问题的。注意,接口方法必须始终是 PUBLIC ABSTRACT ,接口字段必须始终是 PUBLIC STATIC FINAL 。这些修饰符在定义接口时是必要的:

TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
        .initializer("$S", "change")
        .build())
    .addMethod(MethodSpec.methodBuilder("beep")
        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
        .build())
    .build();
复制代码

但是这些修饰符在生成代码时被省略。这些是默认设置,所以得益于 javac ,我们不需要包含它们!

public interface HelloWorld {
    String ONLY_THING_THAT_IS_CONSTANT = "change";

    void beep();
}
复制代码

枚举

使用 enumBuilder 创建枚举类型,并为每个值添加 addEnumConstant()

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK")
    .addEnumConstant("SCISSORS")
    .addEnumConstant("PAPER")
    .build();
复制代码

将生成:

public enum Roshambo {
    ROCK,

    SCISSORS,

    PAPER
}
复制代码

支持复杂枚举,其中枚举值覆盖方法或调用超类构造函数。下面是一个综合的例子:

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
        .addMethod(MethodSpec.methodBuilder("toString")
            .addAnnotation(Override.class)
            .addModifiers(Modifier.PUBLIC)
            .addStatement("return $S", "avalanche!")
            .returns(String.class)
            .build())
        .build())
    .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
        .build())
    .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
        .build())
    .addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
    .addMethod(MethodSpec.constructorBuilder()
        .addParameter(String.class, "handsign")
        .addStatement("this.$N = $N", "handsign", "handsign")
        .build())
    .build();
复制代码

将生成:

public enum Roshambo {
    ROCK("fist") {
        @Override
        public String toString() {
            return "avalanche!";
        }
    },

    SCISSORS("peace"),

    PAPER("flat");

    private final String handsign;

    Roshambo(String handsign) {
        this.handsign = handsign;
    }
}
复制代码

匿名内部类

在枚举代码中,我们使用了 TypeSpec.anonymousInnerClass() ,匿名内部类也可以用在代码块中,它们是可以用 $L 引用的值:

TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
    .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
    .addMethod(MethodSpec.methodBuilder("compare")
        .addAnnotation(Override.class)
        .addModifiers(Modifier.PUBLIC)
        .addParameter(String.class, "a")
        .addParameter(String.class, "b")
        .returns(int.class)
        .addStatement("return $N.length() - $N.length()", "a", "b")
        .build())
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addMethod(MethodSpec.methodBuilder("sortByLength")
        .addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
        .addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
        .build())
    .build();
复制代码

这将生成一个方法,其中包含一个包含方法的类:

void sortByLength(List<String> strings) {
    Collections.sort(strings, new Comparator<String>() {
        @Override
        public int compare(String a, String b) {
            return a.length() - b.length();
        }
    });
}
复制代码

定义匿名内部类的一个特别棘手的部分是超类构造函数的参数。在上面的代码中,我们传递了没有参数的空字符串: TypeSpec.anonymousClassBuilder("") 。要传递不同的参数,请使用带有逗号的 JavaPoet 代码块语法来分隔参数。

注解

简单的注解很容易编写:

MethodSpec toString = MethodSpec.methodBuilder("toString")
    .addAnnotation(Override.class)
    .returns(String.class)
    .addModifiers(Modifier.PUBLIC)
    .addStatement("return $S", "Hoverboard")
    .build();
复制代码

它用 @Override 注解生成这个方法:

@Override
public String toString() {
    return "Hoverboard";
}
复制代码

使用 AnnotationSpec.builder() 在注解上设置属性:

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(Headers.class)
        .addMember("accept", "$S", "application/json; charset=utf-8")
        .addMember("userAgent", "$S", "Square Cash")
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class)
    .build();
复制代码

将会生成带有 acceptuserAgent 属性的注解:

@Headers(
    accept = "application/json; charset=utf-8",
    userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);
复制代码

注解值可以是注解本身,使用 $L嵌入注解:

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(HeaderList.class)
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "Accept")
            .addMember("value", "$S", "application/json; charset=utf-8")
            .build())
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "User-Agent")
            .addMember("value", "$S", "Square Cash")
            .build())
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class)
    .build();
复制代码

将生成:

@HeaderList({
    @Header(name = "Accept", value = "application/json; charset=utf-8"),
    @Header(name = "User-Agent", value = "Square Cash")
})
LogReceipt recordEvent(LogRecord logRecord);
复制代码

注意,可以使用相同的属性名多次调用 addMember() 来填充该属性的值列表。

Javadoc

可以用 Javadoc 记录字段、方法和类型:

MethodSpec dismiss = MethodSpec.methodBuilder("dismiss")
    .addJavadoc("Hides {@code message} from the caller's history. Other\n"
        + "participants in the conversation will continue to see the\n"
        + "message in their own history unless they also delete it.\n")
    .addJavadoc("\n")
    .addJavadoc("<p>Use {@link #delete($T)} to delete the entire\n"
        + "conversation for all participants.\n", Conversation.class)
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addParameter(Message.class, "message")
    .build();
复制代码

将生成:

/**
  * Hides {@code message} from the caller's history. Other
  * participants in the conversation will continue to see the
  * message in their own history unless they also delete it.
  *
  * <p>Use {@link #delete(Conversation)} to delete the entire
  * conversation for all participants.
  */
 void dismiss(Message message);
复制代码

在引用 Javadoc 中的类型时使用 $T 来获得自动导入。

猜你喜欢

转载自juejin.im/post/5df86ad6e51d4558242713cd