JavaPoet

JavaPoet是一个强大的库,用于构建和生成Java源代码。它可以理解类型、管理导入,支持组合生成复杂的代码结构。通过MethodSpec、TypeSpec和JavaFile等类,可以轻松创建类、方法、参数、注解等。本文展示了如何使用JavaPoet生成一个简单的Java程序,并详细解释了占位符如$L、$S、$T和$N的用法,以及如何创建抽象方法、构造方法、参数、注解和Javadoc。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

JavaPoet是一款可以自动生成Java文件的第三方依赖。JavaPoet是JavaWriter的继承者。新项目应该首选JavaPoet,因为它有一个更强大的代码模型:它能理解类型并能自动管理导入。JavaPoet也更适合于组合:与其将一个.java文件的内容自上而下地一次性流传,不如将一个文件组装成一棵声明树。

引入

这里介绍两种项目中的引入方式
在maven项目中的引入方式:在pom.xml中添加

 <dependency>
      <groupId>com.squareup</groupId>
      <artifactId>javapoet</artifactId>
      <version>1.13.0</version>
</dependency>

在Android项目中的引入方式:在需要使用Javapoet的gradle中添加

compile 'com.squareup:javapoet:1.13.0'

其它Java语言的项目中应该也是在相关配置文件中引入就行

简单范例

我们可以使用javapoet生成以下代码

package com.example.helloJavaPoet;
import java.lang.String;
import java.lang.System;
public final class HelloJavaPoet {
  public static void main(String[] args) {
    
    System.out.println("Hello, JavaPoet!");
  }
}

使用以下代码可以生成上述代码

import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class HelloWorld {
    public static void main(String[] args) {
        // 给类添加一个方法
        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 代表一个类
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloJavaPoet")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(main)
                .build();
        // JavaFile 代表 Java 文件
        JavaFile javaFile = JavaFile.builder("com.example.helloJavaPoet", helloWorld)
                .build();
        File outFile = new File("com/example/helloJavaPoet.java");
        if(!outFile.getParentFile().exists()) {
            outFile.getParentFile().mkdirs();
        }
        if (!outFile.exists()) {
            try {
                outFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        FileWriter writer;
        try {
            writer = new FileWriter(outFile.getAbsolutePath());
            writer.write("");//清空原文件内容
            writer.write(javaFile.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}

在这里插入图片描述
因为不是在安卓项目中实现的,所以生成文件上复杂了点,用了File和FileWriter

JavaPoet的常用类

JavaFile————用于生成的Java文件的类
TypeSpec————用于生成类、接口、枚举对象的类
MethodSpec————用于生成方法的类
ParameterSpec————用于生成参数的类
AnnotationSpec————用于生成注解的类
FieldSpec————用于生成变量的类
ClassName————通过包名和类名生成的对象,在JavaPoet中相当于为其指定Class
ParameterizedTypeName————通过MainClass和IncludeClass生成包含泛型的Class

占位符

$L for Literals

$L指的是没有转义的字面值,可以是字符串、基本数据类型、类型声明、注解甚至其他代码块的占位符,就像Formatter的%s。
上面的代码中,将$S换成$L就会不会转换成字符串(就是不加引号),直接以字面量代替
在这里插入图片描述

$S for Strings

$S指的是可以带着引号和转义的字符串的占位符。
(这里和上面的转义还没弄的很懂,官方文档中也只是给了引号的例子)

$T for Types

$T指的是数组类型的占位符,它不仅支持内置类型,还包括自动生成import语句。

例如:
用这个

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


$N for Names

指的是一个名称的占位符,例如调用的方法名称,变量名称(可以是自动生成的方法和变量)。

例如:
用这个代码

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

生成

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

参数

相对参数

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

生成字符串

I ate 3 tacos

位置参数

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

生成字符串同上

名字参数

使用语法$argumentName:X,其中X是上述占位符中的字母,用包含格式字符串中所有参数键的映射调用CodeBlock.addNamed()。参数名使用a-z、A-Z、0-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();
}

生成构造方法

普通方法用的是 MethodSpec.methodBuilder(方法名) 生成,构造方法是用MethodSpec.constructorBuilder()生成

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

方法参数

ParameterSpec.builder() 或 ==MethodSpec的addParameter() ==在方法和构造函数上声明参数。

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

类变量

变量的生成和方法参数也是一样的。用 FieldSpec.builder()TypeSpec的addField()。这里不做解释。

接口

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来创建枚举类型,并用 EnumConstant() 添加值。

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

生成:

public enum Roshambo {
  ROCK,

  SCISSORS,

  PAPER
}

此外,javapoet支持花式枚举,其中枚举值覆盖方法或调用超类构造函数。这里有一个全面的例子

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的代码块语法,用逗号来分隔参数。

注解

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

生成代码

@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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苏觉s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值