当前位置: 首页 > 工具软件 > JavaPoet > 使用案例 >

JavaPoet使用详解

翟善
2023-12-01

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())

Javadoc 对完整的 JavaPoet API 进行了编目,我们将在下面进行探讨。

1.添加依赖

dependencies {
    implementation 'com.squareup:javapoet:1.13.0'
}

2.使用方法

生成java文件,需要的基本元素包括:

  • 创建字段(属性)
  • 创建方法
  • 创建类(匿名类)、接口或枚举
  • 输出文件

接下来一一介绍这些用法。

FieldSpec(字段)

可以使用构建器或使用方便的辅助方法来创建字段,FieldSpec就是用来创建字段的类

FieldSpec.builder(String.class, "android").build()

以上代码等价于String android;

如果添加修饰符可以使用addModifiers

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

其中Modifier包括我们用到的所有修饰符,下面介绍的类,方法都是使用它:

public enum Modifier {
    /** The modifier {@code public} */          PUBLIC,
    /** The modifier {@code protected} */       PROTECTED,
    /** The modifier {@code private} */         PRIVATE,
    /** The modifier {@code abstract} */        ABSTRACT,
    /**
     * The modifier {@code default}
     * @since 1.8
     */
     DEFAULT,
    /** The modifier {@code static} */          STATIC,
    /** The modifier {@code final} */            FINAL,
    /** The modifier {@code transient} */       TRANSIENT,
    /** The modifier {@code volatile} */        VOLATILE,
    /** The modifier {@code synchronized} */    SYNCHRONIZED,
    /** The modifier {@code native} */          NATIVE,
    /** The modifier {@code strictfp} */        STRICTFP;
}

当字段具有 Javadoc、注释或字段初始值设定项时,扩展形式是必需的。字段初始值设定项使用 -like 语法,如果需要初始化参数,使用initializer

FieldSpec.builder(String.class, "android")
	.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
	.initializer("$S", "Android")
	.build()
// private final String android = "Android";


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;

MethodSpec(方法)

MethodSpec用来创建方法,一个最简单的方法包含方法名、返回类型。

MethodSpec.methodBuilder("test")
	.returns(void.class)
	.build()
// 	void test() {}

添加修饰符同上,这里不重复说明,如果添加方法参数,需要使用addParameter

MethodSpec.methodBuilder("test")
	.addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "str")
	.returns(void.class)
	.build()
	
/*public void test(String str) {
}*/

最后就是给方法添加语句,需要使用addStatement:

MethodSpec.methodBuilder("test")
	.addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "str")
    .addStatement("System.out.println(str)")
	.returns(void.class)
	.build()
	
/*public void test(String str) {
	System.out.println(str);
}*/

以上所有方法都有一个代码体。用于获取没有任何主体的方法。这仅在封闭类是抽象类或接口时才合法。

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();
}

泛型

FieldSpec.builder(TypeVariableName.get("T"), "mT", Modifier.PRIVATE).build();
// private T mT;

TypeVariableName mTypeVariable = TypeVariableName.get("T");
ParameterizedTypeName mListTypeName = ParameterizedTypeName.get(ClassName.get(List.class), mTypeVariable);
FieldSpec fieldSpec = FieldSpec.builder(mListTypeName, "mList", Modifier.PRIVATE).build();

//private List<T> mList;

方法和类中使用addTypeVariable添加泛型。

初始化块

TypeSpec.classBuilder("Test")
    .addStaticBlock(CodeBlock.builder().build())
    .addInitializerBlock(CodeBlock.builder().build())
    .build();
    
/*
class Test {
    static {
    }

    {
    }
}*/

addCode和ControlFlow(代码和控制流)

JavaPoet 使用字符串作为代码块:

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;
  }
}

可以看到使用addCode方法,可以一股脑的添加所有代码,但是我们需要自己换行,输入分号和缩进。

手动分号、换行和缩进很乏味,因此 JavaPoet 提供了 API 以使其更容易。有分号和换行符,+ 一起用于大括号、换行符和缩进,
使用addStatement 可以帮我们添加分号和换行,而使用beginControlFlowendControlFlow组合可以帮我们轻松实现控制流代码。所以上面的代码等价于:

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();
}

这是我们调用时得到的:

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

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

某些控制流语句,例如 ,可以具有无限的控制流可能性。您可以使用以下方法处理这些选项:

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");
  }
}

使用捕获异常也是一个用例:

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);
  }
}

其他的使用场景,我举一些例子,大家一看便知:

// do... while
.beginControlFlow("do")
.endControlFlow("while (true)")

// if... else if... else...
.beginControlFlow("if (true)")
.nextControlFlow("else if (false)")
.nextControlFlow("else")
.endControlFlow()

// try... catch... finally
.beginControlFlow("try")
.nextControlFlow("catch ($T e)", Exception.class)
.addStatement("e.printStackTrace()")
.nextControlFlow("finally")
.endControlFlow()

占位符

上面的例子中,我们的代码都是固定的字符串,显得不灵活。因此JavaPoet为我们提供了多种占位符来满足要求。

$S (String)

当代码中包含字符串的时候, 可以使用 $S 表示。

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

$L (Literal)

$L 是字面量替换,它与 $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();
}

$T (Type)

上面例子为了简单,都使用的是一些基础类型,为的是不需要导包。实际中我们需要使用大量对象,如果只是在字符串中写死,代码虽没有问题,但是没有导包还是会保错。这是可以考虑使用$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);

这会生成以下文件,并包含必要的:

package com.example.helloworld;

import java.util.Date;

public final class HelloWorld {
  Date today() {
    return new Date();
  }
}

导入静态

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;
  }
}

$N ( Name)

$N是名称替换。例如我们定义了一个getXXX的方法,

我们调用它时可以使用addStatement(“get$L()”, “XXX”)

这种写法实现,但是每次拼接"get"未免太麻烦了,一个不留心说不定还忘记了。那么使用

addStatement("$N()",methodSpec)就更加方便了。
MethodSpec methodSpec = MethodSpec.methodBuilder("get" + name)
    .returns(String.class)
    .addStatement("return $S", name)
    .build();

MethodSpec.methodBuilder("getValue")
    .returns(String.class)
    .addStatement("return $N()", methodSpec)
    .build();

代码块格式字符串

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

相对参数

将格式字符串中每个占位符的参数值传递给 .在每个示例中,我们生成代码来表示“我吃了 3 个炸玉米饼”

CodeBlock.builder().add("I ate $L $L", 3, "tacos")

位置参数

在格式字符串中的占位符之前放置一个整数索引(从 1 开始)以指定要使用的参数。

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

命名参数

使用语法 where is the format character 并调用包含格式字符串中所有参数键的映射。参数名称使用 、 、 和 中的字符,并且必须以小写字符开头。

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

构造方法

MethodSpec methodSpec = MethodSpec.constructorBuilder()
    .addModifiers(Modifier.PUBLIC)
    .build();

TypeSpec typeSpec = TypeSpec.classBuilder("Test")
    .addMethod(methodSpec)
    .build();

/*
class Test {
    public Test() {
    }
}*/

是一个轻微的用词不当;它也可以用于构造函数:

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 将在输出文件中的方法之前放置构造函数。

参数

使用 or 方便的 API 声明方法和构造函数的参数:

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();

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

void welcomeOverlords(final String android, final String robot) {
}

当参数有注释(如 )时,扩展形式是必要的。

TypeSpec(类,接口,枚举)

TypeSpec用来创建类,接口,或者枚举。

// class Test {}
TypeSpec.classBuilder("Test").build();

//interface Test {}
TypeSpec.interfaceBuilder("Test").build();

/*
enum Test {
    ONE
}*/
TypeSpec typeSpec = TypeSpec.enumBuilder("Test").addEnumConstant("ONE").build();

继承与接口

TypeSpec.classBuilder("Test")
    .superclass(String.class)
    .addSuperinterface(Serializable.class)
    .build();

//class Test extends String implements Serializable {}

接口

JavaPoet 对接口没有任何问题。请注意,接口方法必须始终为 ,接口字段必须始终为 。定义接口时,这些修饰符是必需的:

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();

但是这些修饰符在生成代码时被省略了。这些是默认值,因此我们不需要为了 的利益而包含它们!

public interface HelloWorld {
  String ONLY_THING_THAT_IS_CONSTANT = "change";

  void beep();
}

枚举

用于创建枚举类型,并为每个值:

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 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();
    }
  });
}

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

TypeSpec.anonymousClassBuilder("") // <- 也可添加参数
    .superclass(Runnable.class)
    .addMethod(MethodSpec.methodBuilder("run")
    			   .addModifiers(Modifier.PUBLIC)
                   .addAnnotation(Override.class)
                   .returns(TypeName.VOID)
                   .build())
    .build();

/*
new Runnable() {
    @Override
    public void run() {
    }
}*/

Annotations(注解)

简单的注释很容易,添加注解的方法可以直接使用addAnnotation

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

它使用注释生成此方法:

  @Override
  public String toString() {
    return "Hoverboard";
  }

如果需要给注解设置属性,那么需要使用AnnotationSpec :

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();

使用和属性生成此注释:

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

当您喜欢时,注释值可以是注释本身。用于嵌入式注释:

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);

请注意,您可以使用相同的属性名称多次调用以填充该属性的值列表。

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 中引用类型以获取自动导入时使用。

参考文档
JavaPoet使用攻略
JavaPoet
java基础之反射类型Type
Java泛型参数

 类似资料: