如何在注解处理环境中查找匿名类?
我正在用Java 8编写注解处理器,项目中有如下类:
public class SampleClass { public void foo(int i) { new Runnable() { @Override public void run() { System.out.println(i); } }; } }
其他位置的注解包含SampleClass的全限定名,以及编译后匿名类的唯一标识(格式如SampleClass$id)。我需要找到对应匿名类的javax.lang.model.element.Element对象,退一步说,能获取类中所有匿名类的Element列表也行。
我尝试了递归遍历类的封闭元素的方法:
public List<Element> findAnonymousClasses(String fqn, ProcessingEnvironment env) { Element sampleClassElement = env.getElementUtils().getTypeElement(fqn); List<Element> anons = new ArrayList<>(); sampleClassElement.getEnclosedElements() .forEach(e -> { if(e.getSimpleName().length() == 0) anons.add(e); anons.addAll(findAnonymousClasses(e, env)); }); return anons; }
依据是getSimpleName()的文档:
若代表匿名类或实例初始化器,将返回空名称。
空{@code Name}的长度为0。
后来换成判断e instanceof TypeElement && ((TypeElement) e).getNestingKind() == NestingKind.ANONYMOUS,结果依然为空。排查发现Element.getEnclosedElements()根本不包含匿名类,想知道正确的获取方式。
1. 直接通过匿名类全限定名获取Element
如果你已经知道匿名类的完整限定名(比如SampleClass$1),最直接的方式是用Elements.getTypeElement()直接获取:
TypeElement anonymousClassElement = env.getElementUtils().getTypeElement("com.yourpackage.SampleClass$1"); if (anonymousClassElement != null && anonymousClassElement.getNestingKind() == NestingKind.ANONYMOUS) { // 找到目标匿名类 }
这种方式简单直接,只要你能准确拿到匿名类的编译后名称,就能直接定位到对应的Element。
2. 遍历语法树查找匿名类
匿名类是方法/构造器内部的语法节点,不属于类的直接封闭元素(getEnclosedElements()只返回类级别的成员:字段、方法、内部类等,不包含方法内的局部类/匿名类)。要找到它们,需要访问编译时的语法树:
在Java 8中,可以通过ProcessingEnvironment获取JavacProcessingEnvironment,进而拿到语法树的访问入口:
import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.NewClassTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreeScanner; import com.sun.source.util.Trees; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.TypeElement; import java.util.ArrayList; import java.util.List; import java.util.Set; public class YourProcessor extends AbstractProcessor { private Trees trees; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); trees = Trees.instance(processingEnv); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { TypeElement sampleClassElement = processingEnv.getElementUtils().getTypeElement("com.yourpackage.SampleClass"); if (sampleClassElement == null) return false; TreePath classPath = trees.getPath(sampleClassElement); CompilationUnitTree compilationUnit = classPath.getCompilationUnit(); List<TypeElement> anonymousClasses = new ArrayList<>(); compilationUnit.accept(new TreeScanner<Void, Void>() { @Override public Void visitNewClass(NewClassTree node, Void p) { // 检查是否是匿名类(无类名的NewClass节点) if (node.getIdentifier() == null) { TreePath anonPath = new TreePath(classPath, node.getClassBody()); Element anonElement = trees.getElement(anonPath); if (anonElement instanceof TypeElement) { anonymousClasses.add((TypeElement) anonElement); } } return super.visitNewClass(node, p); } }, null); // anonymousClasses中即为SampleClass内所有匿名类的Element return true; } }
注意:这种方式依赖Sun/Oracle的com.sun.source内部API,在OpenJDK等主流JDK实现中兼容,但非Oracle系编译器可能无法使用。
3. 通过TypeMirror关联查找
如果你能从注解中获取到匿名类的TypeMirror,可以用Types.asElement()直接转换为Element:
TypeMirror anonymousType = ...; // 从注解参数中解析得到的匿名类TypeMirror Element anonElement = env.getTypeUtils().asElement(anonymousType); if (anonElement instanceof TypeElement && ((TypeElement) anonElement).getNestingKind() == NestingKind.ANONYMOUS) { // 处理目标匿名类 }
Element.getEnclosedElements()仅返回类的直接成员(如非匿名内部类、字段、方法等),不会包含方法内部定义的匿名类或局部类。- 匿名类的
NestingKind确实为ANONYMOUS,但因作用域在方法内部,无法通过类的封闭元素列表遍历获取,只能通过语法树访问或全限定名直接定位。
内容的提问来源于stack exchange,提问作者zaaarf

