原文:https://blog.csdn.net/crazy1235/article/details/51876192

JavaPoet

JavaPoet 是一个用来生成 .java源文件的Java API。

当做如注解或者数据库模式、协议格式等事情时,生成源文件就比较有用处。

Example

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

通过MethodSpec类来创建一个”main”方法,并配置了修饰符、返回值类型、参数以及代码语句。然后把这个main方法添加到 HelloWorld 类中,最后添加到 HelloWorld.java文件中。 
这个例子中,我们将文件通过Sytem.out 进行输出,但是同样也可以使用(JavaFile.toString()) 得到string字符串,或者通过 (JavaPoet.writeTo()) 方法写入到文件系统中。

Javadoc 中包括了完整的JavaPoet API, 我们接着往下看。

Code & Control Flow

大多数JavaPoet的API使用的是简单的不可变的Java对象。通过建造者模式,链式方法,可变参数是的API比较友好。JavaPoet提供了(TypeSpec)用于创建类或者接口,(FieldSpec)用来创建字段,(MethodSpec)用来创建方法和构造函数,(ParameterSpec)用来创建参数,(AnnotationSpec)用于创建注解。

但是如果没有语句类,没有语法结点数,可以通过字符串来构建代码块:

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

这个例子稍微有点差劲。生成的代码如下:

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 = " + from + "; i < " + to + "; i++)")
.addStatement("result = result " + op + " i")
.endControlFlow()
.addStatement("return result")
.build();
}

调用computeRange("multiply10to20", 10, 20, "*")就生成如下代码:

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

方法生成方法!JavaPoet生成的是源代码而不是字节码,所以可以通过阅读源码确保正确。

$L for Literals

字符串连接的方法beginControlFlow() 和 addStatement是分散开的,操作较多。 
针对这个问题, JavaPoet 提供了一个语法但是有违String.format()语法. 通过 $L 来接受一个 literal 值。 这有点像 Formatter’s %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();
}

Literals 直接写在输出代码中,没有转义。 它的类型可以是字符串、primitives和一些接下来要说的JavaPoet类型。

$S for Strings

当输出的代码包含字符串的时候, 可以使用 $S 表示一个 string。 下面的代码包含三个方法,每个方法返回自己的名字:

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

输出结果如下:

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

$T for Types

使用Java内置的类型会使代码比较容易理解。JavaPoet极大的支持这些类型,通过 $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);

自动完成import声明,生成代码如下:

package com.example.helloworld;

import java.util.Date;

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

再举一个相似的例子,但是应用了一个不存在的类:

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

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

类不存在,但是代码是完整的:

package com.example.helloworld;

import com.mattel.Hoverboard;

public final class HelloWorld {
Hoverboard tomorrow() {
return new Hoverboard();
}
}

ClassName 这个类非常重要, 当你使用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将每一种类型进行分解,并尽可能的导入其声明.

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

Import static

JavaPoet支持import static。它显示的收集类型成员的名称。例子如下:

...
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将会首先添加 import static 代码块进行配置,当然也需要导入其他所需的类型引用。

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 for Names

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

生成的代码如下,在byteToHex()方法中通过$N来引用 hexDigit()方法作为一个参数:

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

Methods

上面的例子中的方法都有方法体。 使用 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平台上不可用. 这只限制与生成代码阶段;输出的代码可运行在任何平台上: JVMs, Android, 
and GWT。

方法可能会有参数,异常,可变参数,注释,注解,类型变量和一个返回类型。这些都可以通过 MethodSpec.Builder 来进行配置。

Constructors

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

多数情况下,构造方法同普通方法一样。当生成代码时,构造函数会先于其他方法生成。

Parameters

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

虽然上面的代码生成android 和 robot这两个参数是不同的方式,但是输出是一样的:

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

当参数有注解(比如 @Nullable)的时候,通过扩展的 Builder 方式创建参数是比较方便的。

Fields

字段通参数一样通过 build 方式创建:

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

通关 Builder 方式很容易生成带注释、注解或者初始化的字段。 
Field的初始化代码如下:

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;

Interfaces

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

但是这些修饰符在生成的java文件中是找不到的。这些都是缺省值。

public interface HelloWorld {
String ONLY_THING_THAT_IS_CONSTANT = "change"; void beep();
}

Enums

通过 enumBuilder 可以创建枚举类型, 调用 addEnumConstant() 可添加枚举变量:

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

生成如下代码:

public enum Roshambo {
ROCK, SCISSORS, PAPER
}

Fancy enums 也是支持的。

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!")
.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 void toString() {
return "avalanche!";
}
}, SCISSORS("peace"), PAPER("flat"); private final String handsign; Roshambo(String handsign) {
this.handsign = handsign;
}
}

Anonymous Inner Classes

在上面的枚举代码汇总,使用了 Types.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("")

Annotations

对方法添加注解非常简单:

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

变量方法和类都可以添加注释:

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

使用 $T 可以自动导入类型的引用。

Download

下载最新的 the latest .jar

或者添加Maven依赖:

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

或者Gradle依赖:

compile 'com.squareup:javapoet:1.7.0'

Snapshots of the development version are available in Sonatype’s snapshots repository.

JavaPoet的基本使用的更多相关文章

  1. Android关于AutoService、Javapoet讲解

    一.上篇文章提到自定义processor中用到AutoService 文章中我们用到了AutoService, 使用@AutoService(Processor.class),编译后 MethodSp ...

  2. Javapoet源码解析

    Javapoet:是生成.java源文件的开源API  github:https://github.com/square/javapoet   以生成一个HelloWrold.java文件为例: pa ...

  3. JavaPoet开源项目的使用

    一.介绍 JavaPoet项目可以动态的生成Java文件,这是一个很强大和很动态的方法,使用注解的时候假如需要生成新的Java文件就可以通过这个开源项目实现. 项目地址:https://github. ...

  4. 深入Dagger:JavaPoet的使用

    前言 最近在用Dagger开发应用,Dagger是google在square的基础上去反射的依赖注入框架. Dagger会根据定义的注解在编译阶段根据依赖注入的配置生成相应的代码,来减少运行期间反射的 ...

  5. Android注解使用之通过annotationProcessor注解生成代码实现自己的ButterKnife框架

    前言: Annotation注解在Android的开发中的使用越来越普遍,例如EventBus.ButterKnife.Dagger2等,之前使用注解的时候需要利用反射机制势必影响到运行效率及性能,直 ...

  6. apt 根据注解,编译时生成代码

    apt: @Retention后面的值,设置的为CLASS,说明就是编译时动态处理的.一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的~~ ...

  7. ANNOTATION PROCESSING 101 by Hannes Dorfmann — 10 Jan 2015

    原文地址:http://hannesdorfmann.com/annotation-processing/annotationprocessing101 In this blog entry I wo ...

  8. 【腾讯Bugly干货分享】深入理解 ButterKnife,让你的程序学会写代码

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/578753c0c9da73584b025875 0.引子 话说我们做程序员的,都 ...

  9. Java注解处理器(转)

    Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...

随机推荐

  1. Android 属性property_get/property_set

    每个属性都有一个名称和值,他们都是字符串格式.属性被大量使用在Android系统中,用来记录系统设置或进程之间的信息交换.属性是在整个系统中全局可见的.每个进程可以get/set属性. 在系统初始化时 ...

  2. git 只提交部分修改文件

    1.git status //查看修改文件状态 2.git add  //将想要提交的文件add到本地库 4.git status  //查看修改文件状态 3.git commit  //提交add到 ...

  3. 记某app内购破解 – 安卓逆向菜鸟的初体验

    前言 因为某个机缘,我拿到一个赛车app,玩了一会想买个装备,居然要我掏钱包,作为一名cracker,我觉得我的尊严受到了严重的蔑视(无奈钱包空空),我觉得要捍卫我那脆弱的玻璃心(钱包),所以,开干吧 ...

  4. vue -- router路由跳转错误 , NavigationDuplicated

    vue-router ≥3.0版本回调形式以及改成promise api的形式了,返回的是一个promise,如果路由地址跳转相同, 且没有捕获到错误,控制台始终会出现如图所示的警告 (注:3.0以下 ...

  5. 【mysql】开启远程连接

    设置root用户可以远程连接 use mysql; update user set host = '%' where user = 'root'; FLUSH PRIVILEGES;

  6. nodepad++格式化html代码

    如果没有安装插件

  7. appium 弹窗处理

    测试过程中遇到两类弹窗: 系统权限弹窗具体业务弹窗系统权限弹窗Android系统权限弹窗一般出现在安装 app 后首次打开,如:定位权限.电话权限等.我们可以按顺序执行测试用例,将该类操作放到 Ini ...

  8. Oracle和Mysql中的字符串的拼接

    SQL允许两个或者多个字段之间进行计算,字符串类型的字段也不例外.比如我们需要 以"工号+姓名"的方式在报表中显示一个员工的信息,那么就需要把工号和姓名两个字符 串类型的字段拼接计 ...

  9. 遇到了NameError: name ‘name’ is not defined 这样的错误。

    改正:__name__ == "__main__" name的左右两边各有两条下划线,不是左右两边各有一条

  10. POJ 1661 Help Jimmy(C)动态规划

    没刷过 POJ,这题是论坛有人问的,我才看看. 我发现 POJ 注册很奇怪,账号总是登不上去,弄的我还注册两个.Emmm 首次体验很差,还好我不在 POJ 刷题. 题目链接:POJ 1661 Help ...