Android JavaOpet

Android/Java 框架常常会使用编译期注解, 而其中关键的一步是如何生成 Java 文件. JavaPoet 就是用来生成 Java 代码的一个 Java Library.

基础概念

首先, 让我们来看一个简单的 Java 文件:
在这里插入图片描述
可以见, 一个 Java 文件由四部分组成。

也就是我们要生成一个 java 文件, 就是要生成上述四部分内容。

对应到 JavaPoet 中, 我们来看一张对比图:
在这里插入图片描述
可见,JavaFile对应的Java文件。 包名直接用一个字符串表示。TypeSpec表示一个 class 定义。FieldSpec表示类中的属性。MethodSpec表示类中的方法。

最简实例

理解了JavaPoet的大致结构, 我们就可以生成一个简单的Java文件了。

gradle 引用:

compile 'com.squareup:javapoet:1.8.0'
public class HowToJavaPoetDemo {
    public static void main(String[] args) {
        // `JavaFile` 代表 Java 文件
        JavaFile javaFile = JavaFile.builder("com.walfud.howtojavapoet",
            // TypeSpec 代表一个类
            TypeSpec.classBuilder("Clazz")
                // 给类添加一个属性
                .addField(FieldSpec.builder(int.class, "mField", Modifier.PRIVATE)
                    .build())
                // 给类添加一个方法
                .addMethod(MethodSpec.methodBuilder("method")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addStatement("System.out.println(str)")
                    .build())
                .build())
             .build();

        System.out.println(javaFile.toString());
    }
}

更多的数据类型

上个例子中, 我们为了单一目标, 特意简化了数据类型。 现实编程中绝大多数情况下要面对各种对象类型, 如File/List/Activity(android.app.Activity)等等。 下面我们来了解一下JavaPoet的类型系统。

我们要定义一个java.io.File类型的变量, 比如这样:

import java.io.File;

public class ... {
    private File mFile;
}

我们需要使用FieldSpec.builder(File.class, "mFile", Modifier.PRIVATE).build()来定义这个属性。 这样JavaPoet就可以自动找到 File所在的包并帮我们自动引入import java.io.File

但如果定义List<String>这种泛型的话, 该怎么写呢?List<String>.class显然是不行的。 因为这是一个复合类型, 其中的参数类型无法被表示。

因此,JavaPoet帮我们定义了如下几种专门描述类型的类。其关系图如下:

共有六种类型, 我们举几个常见的例子:

分类生成的类型JavaPoet 写法也可以这么写 (等效的 Java 写法)
内置类型intTypeName.INTint.class
数组类型int[]ArrayTypeName.of(int.class)int[].class
需要引入包名的类型java.io.FileClassName.get(“java.io”, “File”)java.io.File.class
参数化类型 (ParameterizedType)ListParameterizedTypeName.get(List.class, String.class)
类型变量 (WildcardType)用于声明泛型TTypeVariableName.get(“T”)
通配符类型? extends StringWildcardTypeName.subtypeOf(String.class)

这些类型之间可以相互嵌套, 比如ParameterizedTypeName.get(List.class, String.class)其中List.class等价于 ClassName.get("java.util", "List")。 因此,ParameterizedTypeName.get(List.class, String.class)
可以写为ParameterizedTypeName.get(ClassName.get("java.util", "List"), ClassName.get("java.lang", "String"))

前者的好处是简洁, 后者的好处是 “使用ClassName代表某个类型而无需引入该类型“。 比如: 由于在java工程中是没有android的sdk, 所以你在java工程中想生成android.app.Activity这种类型是不能直接Activity.class。这种情况下只能通过ClassName 进行引用。”

在 Statement 中使用类型

上面介绍了JavaPoet的类型系统,现在我们来看看最后一道坎: statemenet中的类型占位符。

我们先来看看statement占位符在哪里使用:

MethodSpec.methodBuilder("method")
    .addStatement("$T file", File.class)            // File file;
    .addStatement("$L = null", "file")              // file = null;
    .addStatement("file = new File($S)", "foo/bar") // file = new File("foo/bar");
    .build();

$T是类型替换, 一般用于("$T foo", List.class)=>List foo. $T的好处在于JavaPoet会自动帮你补全文件开头的 import. 如果直接写("List foo")虽然也能生成List foo, 但是最终的java文件就不会自动帮你添加import java.util.List.
$L是字面量替换, 比如("abc$L123", "FOO")=>abcFOO123. 也就是直接替换.
$S 是字符串替换, 比如:("$S.length()", "foo")=>"foo".length()注意$S是将参数替换为了一个带双引号的字符串. 免去了手写"\"foo\".length()"中转义(\")的麻烦.
$N 是名称替换, 比如你之前定义了一个函数MethodSpec methodSpec = MethodSpec.methodBuilder("foo").build();现在你可以通过$N获取这个函数的名称("$N", methodSpec)=>foo.

万能例子

最后, 我们展示一段几乎涵盖你所常见case的例子 (仅为了展示JavaPoet用法,生成的代码可能编译不过). 如果哪里不知道怎么生成的, 可以方便的在下面的生成代码里查找生成方法.

package com.walfud.howtojavapoet;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.Comparable;
import java.lang.Exception;
import java.lang.Integer;
import java.lang.Object;
import java.lang.Override;
import java.lang.Runnable;
import java.lang.RuntimeException;
import java.lang.String;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class Clazz<T> extends String implements Serializable, Comparable<String>, Map<T, ? extends String> {
    static {
    }

    private int mInt;

    private int[] mArr;

    private File mRef;

    private T mT;

    private List<String> mParameterizedField;

    private List<? extends String> mWildcardField;

    {
    }

    public Clazz() {
    }

    @Override
    public <T> int method(String string, T t, Map<Integer, ? extends T> map) throws IOException,
            RuntimeException {
        int foo = 1;
        String bar = "a string";
        Object obj = new HashMap<Integer, ? extends T>(5);
        baz(new Runnable(String param) {
                @Override
                void run() {
                }
            });
        for (int i = 0; i < 5; i++) {
        }
        while (false) {
        }
        do {
        } while (false);
        if (false) {
        } else if (false) {
        } else {
        }
        try {
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
        return 0;
    }

    class InnerClass {
    }
}

JavaPoet 代码:

package com.walfud.howtojavapoet;


import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.lang.model.element.Modifier;

/**
 * http://android.walfud.com/javapoet-看这一篇就够了/
 */
class HowToJavaPoetDemo {
    public static void main(String[] args) {
        TypeSpec clazz = clazz(builtinTypeField(),          // int
                               arrayTypeField(),            // int[]
                               refTypeField(),              // File
                               typeField(),                 // T
                               parameterizedTypeField(),    // List<String>
                               wildcardTypeField(),         // List<? extends String>
                               constructor(),               // 构造函数
                               method(code()));             // 普通方法
        JavaFile javaFile = JavaFile.builder("com.walfud.howtojavapoet", clazz).build();

        System.out.println(javaFile.toString());
    }

    /**
     * `public abstract class Clazz<T> extends String implements Serializable, Comparable<String>, Comparable<? extends String> {
     * ...
     * }`
     *
     * @return
     */
    public static TypeSpec clazz(FieldSpec builtinTypeField, FieldSpec arrayTypeField, FieldSpec refTypeField,
                 FieldSpec typeField, FieldSpec parameterizedTypeField, FieldSpec wildcardTypeField,
                 MethodSpec constructor, MethodSpec methodSpec) {
        return TypeSpec.classBuilder("Clazz")
                    // 限定符
                    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
                    // 泛型
                    .addTypeVariable(TypeVariableName.get("T"))

                    // 继承与接口
                    .superclass(String.class)
                    .addSuperinterface(Serializable.class)
                    .addSuperinterface(ParameterizedTypeName.get(Comparable.class, String.class))
                    .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Map.class), 
                                                         TypeVariableName.get("T"), 
                                                         WildcardTypeName.subtypeOf(String.class)))

                    // 初始化块
                    .addStaticBlock(CodeBlock.builder().build())
                    .addInitializerBlock(CodeBlock.builder().build())

                    // 属性
                    .addField(builtinTypeField)
                    .addField(arrayTypeField)
                    .addField(refTypeField)
                    .addField(typeField)
                    .addField(parameterizedTypeField)
                    .addField(wildcardTypeField)

                    // 方法 (构造函数也在此定义)
                    .addMethod(constructor)
                    .addMethod(methodSpec)

                    // 内部类
                    .addType(TypeSpec.classBuilder("InnerClass").build())

                    .build();
    }

    /**
     * 内置类型
     */
    public static FieldSpec builtinTypeField() {
        // private int mInt;
        return FieldSpec.builder(int.class, "mInt", Modifier.PRIVATE).build();
    }

    /**
     * 数组类型
     */
    public static FieldSpec arrayTypeField() {
        // private int[] mArr;
        return FieldSpec.builder(int[].class, "mArr", Modifier.PRIVATE).build();
    }

    /**
     * 需要导入 import 的类型
     */
    public static FieldSpec refTypeField() {
        // private File mRef;
        return FieldSpec.builder(File.class, "mRef", Modifier.PRIVATE).build();
    }

    /**
     * 泛型
     */
    public static FieldSpec typeField() {
        // private File mT;
        return FieldSpec.builder(TypeVariableName.get("T"), "mT", Modifier.PRIVATE).build();
    }

    /**
     * 参数化类型
     */
    public static FieldSpec parameterizedTypeField() {
        // private List<String> mParameterizedField;
        return FieldSpec.builder(ParameterizedTypeName.get(List.class, String.class),
                                 "mParameterizedField",
                                 Modifier.PRIVATE)
                .build();
    }

    /**
     * 通配符参数化类型
     *
     * @return
     */
    public static FieldSpec wildcardTypeField() {
        // private List<? extends String> mWildcardField;
        return FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(List.class),
                               WildcardTypeName.subtypeOf(String.class)),
                                 "mWildcardField",
                                 Modifier.PRIVATE)
                .build();
    }

    /**
     * 构造函数
     */
    public static MethodSpec constructor() {
        return MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .build();
    }

    /**
     * `@Override
     * public <T> Integer method(String string, T t, Map<Integer, ? extends T> map) throws IOException, RuntimeException {
     * ...
     * }`
     *
     * @param codeBlock
     * @return
     */
    public static MethodSpec method(CodeBlock codeBlock) {
        return MethodSpec.methodBuilder("method")
                .addAnnotation(Override.class)
                .addTypeVariable(TypeVariableName.get("T"))
                .addModifiers(Modifier.PUBLIC)
                .returns(int.class)
                .addParameter(String.class, "string")
                .addParameter(TypeVariableName.get("T"), "t")
                .addParameter(ParameterizedTypeName.get(ClassName.get(Map.class), 
                                                        ClassName.get(Integer.class), 
                                                        WildcardTypeName.subtypeOf(TypeVariableName.get("T"))), 
                              "map")
                .addException(IOException.class)
                .addException(RuntimeException.class)
                .addCode(codeBlock)
                .build();
    }

    /**
     * ‘method’ 方法中的具体语句
     */
    public static CodeBlock code() {
        return CodeBlock.builder()
                .addStatement("int foo = 1")
                .addStatement("$T bar = $S", String.class, "a string")

                // Object obj = new HashMap<Integer, ? extends T>(5);
                .addStatement("$T obj = new $T(5)", 
                              Object.class, ParameterizedTypeName.get(ClassName.get(HashMap.class), 
                                                                      ClassName.get(Integer.class), 
                                                                      WildcardTypeName.subtypeOf(TypeVariableName.get("T"))))

                // method(new Runnable(String param) {
                //   @Override
                //   void run() {
                //   }
                // });
                .addStatement("baz($L)", TypeSpec.anonymousClassBuilder("$T param", String.class)
                        .superclass(Runnable.class)
                        .addMethod(MethodSpec.methodBuilder("run")
                                .addAnnotation(Override.class)
                                .returns(TypeName.VOID)
                                .build())
                        .build())

                // for
                .beginControlFlow("for (int i = 0; i < 5; i++)")
                .endControlFlow()

                // while
                .beginControlFlow("while (false)")
                .endControlFlow()

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

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

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

                .addStatement("return 0")
                .build();
    }
}

为了方便记忆, 我总结了一张图, 标注了关键或者稍微复杂的那些用法:
在这里插入图片描述
注意: 在Android工程中, 是无法使用javax.lang.model.element.Modifier包的, 因为Android运行时没有相关的class.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值