概述
编译器分类:
- 前端编译器:把 *.java 文件转变成 .class 文件的过程;
- JIT编译器:把字节码转变成机器码;
- AOT编译器:直接把 *.java 文件编译成本地机器码;
Javac编译器
-
解析与填充符号表
①词法、语法分析:词法分析是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写的最小元素,而标记则是编译过程的最小元素;语法分析是根据Token序列来构造抽象语法树(描述程序代码语法结构的树形表示方式)的过程。
②填充符号表:符号表是由一组符号地址和符号信息构成的一组表格,符号表是地址分配的依据。 -
注解处理器:可以读取、修改、添加抽象语法树中的任意元素。
-
语义分析与字节码生成:语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,语义分析分为标注检查和数据及控制流分析。
①标注检查:检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配,等等;还有一个重要动作是常量折叠,如 *int a=1+2;*会折叠为 int a=3;
②数据及控制流分析:是对程序上下文逻辑更进一步的验证,它可以检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否有返回值、是否所有的受查异常都被正确处理了等问题。
③语法糖:指计算机语言中添加的某种语法,增加程序可读性。
④字节码生成:将前面生成的信息转换成字节码写到磁盘中。
Java语法糖的味道
- 泛型与类型擦除:Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。
- 自动装箱、拆箱与循环遍历:
- 条件编译:使用条件为常量的if语句。
除去上述3中之外,还有其他语法糖,如内部类、枚举类、断言语句、对枚举和字符串的switch支持、在try语句中定义和关闭资源。
插入式注解处理器
实战处理器:NameCheckProcessor(对程序命名进行检查)
- 目标
主要解决程序写的好不好 - 实现
继承抽象类javax.annotation.processing.AbstractProcessor,覆盖方法process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv),从第一个参数中可以获取此注解处理器所要处理的注解集合,第二个参数可以访问到当前的语法树节点,每个语法树节点表示为一个Element;如果不需要改变或生成语法树的内容,就返回一个false的布尔值。
init(ProcessingEnvironment processingEnv)参数表示注解处理器的上下文环境,要创建新的代码、向编译器输出信息、获取其他工具类等都需要用到这个实例变量。
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
/**
* 命名检查器<br/>
* <p><a href="NameCheckProcessor.java.html"><i>view source</i></a></p>
* @version 0.1
* @author <a href="mailto:zhangzuol@gnnt.com.cn">张祚良</a>
*
*/
// "*"表示支持所有的Annotation
@SupportedAnnotationTypes("*")
// 支持jdk1.8的java代码
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class NameCheckProcessor extends AbstractProcessor {
private NameChecker nameChecker;
/**
* 初始化名称检查插件
* */
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
nameChecker = new NameChecker(processingEnv);
}
/**
* 对输入的各语法树节点进行名称检查
* */
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
//判断节点是否存在
if(!roundEnv.processingOver()){
//循环节点进行名称检查
for(Element element : roundEnv.getRootElements()){
nameChecker.checkName(element);
}
}
return false;
}
}
import java.util.EnumSet;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementScanner8;
import javax.tools.Diagnostic.Kind;
/**
* 程序名称规范的编译器插件,如果程序名称不符合规范,将会输出一个编译器的warning信息<br/>
* <p><a href="NameChecker.java.html"><i>view source</i></a></p>
* @version 0.1
* @author <a href="mailto:zhangzuol@gnnt.com.cn">张祚良</a>
*
*/
public class NameChecker {
private final Messager messager;
NameCheckScanner nameCheckScanner = new NameCheckScanner();
NameChecker(ProcessingEnvironment processingEnv){
this.messager = processingEnv.getMessager();
}
/**
* 对Java程序命名进行检查
* 1.类或接口:驼峰命名,首字母大写
* 2.方法:驼峰命名,首字母小写
* 3.字段:驼峰命名,首字母小写
* 4.常量:全部大写
* */
public void checkName(Element element){
nameCheckScanner.scan(element);
}
/**
* 名称检查器实现类,继承了jdk1.8中的ElementScanner8,将会以Visitor模式访问抽象语法树中的元素<br/>
* <p><a href="NameChecker.java.html"><i>view source</i></a></p>
* @version 0.1
* @author <a href="mailto:zhangzuol@gnnt.com.cn">张祚良</a>
*
*/
private class NameCheckScanner extends ElementScanner8<Void, Void>{
/**
* 此方法用于检查Java类
* */
@Override
public Void visitType(TypeElement e, Void p) {
scan(e.getTypeParameters(), p);
checkCamelCase(e,true);
super.visitType(e, p);
return null;
}
/**
* 检查方法命名是否合法
*/
@Override
public Void visitExecutable(ExecutableElement e, Void p) {
if(e.getKind() == ElementKind.METHOD){
Name name = e.getSimpleName();
if(name.contentEquals(e.getEnclosingElement().getSimpleName())){
messager.printMessage(Kind.WARNING, "一个普通方法"+name+"不应该与类名重复,避免与构造函数产生混淆",e);
checkCamelCase(e,false);
}
}
super.visitExecutable(e, p);
return null;
}
/**
* 检查变量名是否合法
*/
@Override
public Void visitVariable(VariableElement e, Void p) {
//如果变量是枚举或常量,则按大写命名检查,否则按驼峰命名检查
if(e.getKind() == ElementKind.ENUM_CONSTANT || e.getConstantValue()!=null || heuristicallyConstant(e))
checkAllCaps(e);
else
checkCamelCase(e,false);
return super.visitVariable(e, p);
}
/**
* 判断一个变量是否是常量
*/
private boolean heuristicallyConstant(VariableElement e) {
if(e.getEnclosingElement().getKind() == ElementKind.INTERFACE)
return true;
else if(e.getKind() == ElementKind.FIELD && e.getModifiers().containsAll(EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)))
return true;
else
return false;
}
/**
* 检查传入元素是否符合驼峰命名法
* initialCaps:true 首字母大写 false首字母小写
*/
private void checkCamelCase(Element e, boolean initialCaps) {
String name = e.getSimpleName().toString();
//防止出现连续连个字母都为大写的情况
boolean previousUpper = false;
boolean conventional = true;
int firstCodePoint = name.codePointAt(0);
if(Character.isUpperCase(firstCodePoint)){
previousUpper = true;
if(!initialCaps){
messager.printMessage(Kind.WARNING, "名称"+name+"应当以小写字母开头",e);
return;
}
}else if(Character.isLowerCase(firstCodePoint)){
if(initialCaps){
messager.printMessage(Kind.WARNING, "名称"+name+"应当以大写字母开头",e);
return;
}
}else{
conventional = false;
}
if(conventional){
int cp = firstCodePoint;
for (int i = Character.charCount(cp); i < name.length(); i+=Character.charCount(cp)) {
cp = name.codePointAt(i);
if(Character.isUpperCase(cp)){
if(previousUpper){
conventional = false;
break;
}
previousUpper = true;
}else{
previousUpper = false;
}
}
}
if(!conventional){
messager.printMessage(Kind.WARNING, "名称"+name+"应当符合驼峰命名法",e);
}
}
/**
* 大写命名检查,要求第一个字母必须是大写英文字母,其余部分可以是下划线或大写字母
*/
private void checkAllCaps(Element e) {
String name = e.getSimpleName().toString();
boolean conventional = true;
int firstCodePoint = name.codePointAt(0);
if(!Character.isUpperCase(firstCodePoint))
conventional = false;
else{
//防止出现2个连续的下划线
boolean previousUnderscore = false;
int cp = firstCodePoint;
for (int i = Character.charCount(cp); i < name.length(); i+=Character.charCount(cp)) {
cp = name.codePointAt(i);
if(cp == (int)'_'){
if(previousUnderscore){
conventional = false;
break;
}
previousUnderscore = true;
}else{
previousUnderscore = false;
if(!Character.isUpperCase(cp) && !Character.isDigit(cp)){
conventional = false;
break;
}
}
}
}
if(!conventional){
messager.printMessage(Kind.WARNING, "名称"+name+"应当全部以大写字母或下划线命名,并且以字母开头",e);
}
}
}
}
/**
* 不规范命名代码样例<br/>
* <p><a href="BADLY_NAMWED_CODE.java.html"><i>view source</i></a></p>
* @version 0.1
* @author <a href="mailto:zhangzuol@gnnt.com.cn">张祚良</a>
*
*/
public class BADLY_NAMWED_CODE {
enum colors{
red,blue,green;
}
static final int _FORTY_TWO = 42;
public static int NOT_A_CONSTANT = _FORTY_TWO;
protected void BADLY_NAME_CODE(){
return;
}
public void NOTcamelCASEmethodNAME(){
return;
}
}
- 运行与测试
我们可以通过Javac命令的“-processor”参数来执行编译时需要附带的注解处理器,在相应的工程下src/java/mian目录下执行以下命令编译
javac -encoding UTF-8 cn/tf/jvm/part10/NameChecker.java
javac -encoding UTF-8 cn/tf/jvm/part10/NameCheckProcessor.java
最后使用编译好的文件进行使用
javac -processor cn.tf.jvm.part10.NameCheckProcessor cn/tf/jvm/part10/BADLY_NAMED_CODE.java
执行结果如下:
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:3: 警告: 名称“BADLY_NAMED_CODE”应当符合驼式命名法(Camel Case Names)
public class BADLY_NAMED_CODE {
^
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:4: 警告: 名称“colors”应当以大写字母开头
enum colors {
^
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:5: 警告: 常量“red”应当全部以大写字母或下划线命名,并且以字母开头
red, blue, green;
^
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:5: 警告: 常量“blue”应当全部以大写字母或下划线命名,并且以字母开头
red, blue, green;
^
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:5: 警告: 常量“green”应当全部以大写字母或下划线命名,并且以字母开头
red, blue, green;
^
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:8: 警告: 常量“_FORTY_TWO”应当全部以大写字母或下划线命名,并且以字母开头
static final int _FORTY_TWO = 66;
^
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:10: 警告: 名称“NOT_A_CONSTANT”应当以小写字母开头
public static int NOT_A_CONSTANT = _FORTY_TWO;
^
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:12: 警告: 一个普通方法 “BADLY_NAMED_CODE”不应当与类名重复,避免与构造函数产生混淆
protected void BADLY_NAMED_CODE() {
^
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:12: 警告: 名称“BADLY_NAMED_CODE”应当以小写字母开头
protected void BADLY_NAMED_CODE() {
^
cn\tf\jvm\part10\BADLY_NAMED_CODE.java:16: 警告: 名称“NOTcamelCASEmethodNAME”应当以小写字母开头
public void NOTcamelCASEmethodNAME() {
^
10 个警告