概述
文章目录
- 1、一个简短故事
- 1.1、生成字节码而不是源码
- 1.2、开发者发挥生成代码的能力
- 2、使得Groovy更简洁
- 2.1、代码生成转换
- 2.2、Class设计和设计模式注解
- 2.3、日志提高
- 2.4、声明并发
- 2.5、容易克隆和扩展
- 2.6、脚本支持
- 2.7、更多转换
- 3、探索AST
- 4、创建ASTs
- 4.1、亲手创建Ast
- 4.2、AstBuilder.buildFromSpec
- 4.3、AstBuilder.buildFromString
- 4.4、AstBuilder.buildFromCode
- 5、本地转换
- 6、全局转换
- 7、测试AST转换
- 8、限制
- 10、总结
AST- abstract syntax tree ( 抽象的语法树)
1、一个简短故事
1.1、生成字节码而不是源码
- groovy自动生成getter,setter方法,变成字节码文件
1.2、开发者发挥生成代码的能力
2、使得Groovy更简洁
2.1、代码生成转换
-
@groovy.transform.ToString, 有点类似Lombok
-
/** * 使用groovy 的@ToString注解 * @author liangchen* @date 2020/11/12 */ class ToString91_92 extends GroovyTestCase{ void testToStringToGenerating(){ def sherLock = new Detective(firtName: "Sherlock", lastName: "Holmes") assert sherLock.toString() == 'com.jack.groovy.ch9.Detective(Sherlock, Holmes)' } void testToStringParameters(){ def nancy = new Sleuth(firstName: 'Nancy',lastName: 'Drew') assert nancy.toString() == 'com.jack.groovy.ch9.Sleuth(firstName:Nancy, lastName:Drew)' // 忽略null值 nancy.lastName=null assert nancy.toString() == 'com.jack.groovy.ch9.Sleuth(firstName:Nancy)' } } @ToString class Detective{ String firtName, lastName } //包括字段名且忽略null的字段 @ToString(includeNames = true, ignoreNulls =true) class Sleuth{ String firstName, lastName }
-
-
@EqualsAndHashCode
-
/** * @EqualAndHashCode * @author liangchen* @date 2020/11/12 */ class EqualsAndHashCode93 extends GroovyTestCase { void testGeneratesEqualAndHashCodeMethods(){ def magneto = new Actor(firstName: "Ian", lastName: "McKellen") def gandalf = new Actor(firstName: "Ian", lastName: "McKellen") assert magneto == gandalf } } @EqualsAndHashCode class Actor{ String firstName, lastName }
-
-
@TupleConstructor 构造方法(各种参数的构造方法)
-
/** * @TupleConstructor 生成各种参数的构造方法 * @author liangchen* @date 2020/11/12 */ class TupleConstructor94 extends GroovyTestCase{ void testTupleConstructorToGenerateJava(){ def al = new Athlete('Michael', 'Jordan') def a2 = new Athlete('Michael') assert al.firstName == a2.firstName } } @TupleConstructor class Athlete{ String firstName, lastName }
-
-
@Lazy 延迟加载
-
/** * 延迟加载 @Lazy 并没有立即实例化,在使用时候才实例化 * @author liangchen* @date 2020/11/12 */ class Lazy95 extends GroovyTestCase{ void testLazyDelayProperty(){ new ResourceMain().with { assert Resource.stats() == '1 alive, 0 used' res2.use() res3.use() res4.use() assert Resource.stats() == '4 alive, 3 used' assert res4 instanceof Resource def expected = 'res4=java.lang.ref.SoftReference' assert it.dump().contains(expected) } } } class Resource{ private static alive = 0 private static used =0 Resource(){ alive++ } def use(){ used++ } static stats(){ "$alive alive, $used used" } } class ResourceMain{ def rea1 = new Resource() @Lazy res2 = new Resource() @Lazy static res3 = {new Resource()}() @Lazy (soft = true) volatile Resource res4 }
-
-
@IndexedProperty List生成数组下标
-
/** * 类似生成数组的下标 * @author liangchen* @date 2020/11/12 */ class IndexedProperty96 extends GroovyTestCase{ void testUsingIndexedProperty(){ def books = ['The Mysterious Affair at Styles','The Murder at the Vicarage'] new Author(name:'Agatha Christie', books:books).with { books[0] = 'Murder on the Orient Express' setBooks(0,'Death on the Nile') assert getBooks(0) == 'Death on the Nile' assert getBooks(1) =='The Murder at the Vicarage' } } } class Author{ String name @IndexedProperty List<String> books }
-
-
@InheritConstructors 自动生成继承父类的构造方法
-
import groovy.test.GroovyTestCase /** * * @author liangchen* @date 2020/11/12 */ class InheritConstructors extends GroovyTestCase{ void testInheritConstructors(){ def pw1 = new MyPrintWriter(new File('out1.txt')) def pw2 = new MyPrintWriter('out2.txt', 'US-ASCII') [pw1, pw2].each { //将foo 字符写到 out1.txt和out2.txt文件中 it << 'foo' //关闭流 it.close() } //获取文本 assert new File('out1.txt').text == new File('out2.txt').text // 删除文件 ['out1.txt','out2.txt'].each{new File(it).delete()} } } /** * 继承父类构造方法 */ @groovy.transform.InheritConstructors class MyPrintWriter extends PrintWriter{}
-
-
@Sortable (排序)
-
import groovy.test.GroovyTestCase import groovy.transform.Sortable /** * @Sortable 注解,有序 * @author liangchen* @date 2020/11/12 */ class Sortable98 extends GroovyTestCase{ void testSortableGenerateComparableMethod() { def politicians = [ new Politician(first: 'Margaret', initial: 'H', last: 'Thatcher'), new Politician(first: 'George', initial: 'W', last: 'Bush'), new Politician(first: 'Jack', initial: 'O', last: 'Bush') ] // 执行排序 def sorted = politicians.toSorted() //执行排序之后调用initials assert sorted*.initials() == ['JOB','GWB',"MHT"] def byInitial = Politician.comparatorByInitial() sorted = politicians.toSorted(byInitial) assert sorted*.initials() == ['MHT','JOB', 'GWB'] } } /** * 排序,首先根据last排序,然后按照initial 排序 */ @Sortable(includes = 'last,initial') class Politician { String first Character initial String last String initials(){ first[0] + initial + last[0] } }
-
-
@Builder注解 ,建造模式创建对象
-
/** * 建造模式创建对象, 链式调用 * @author liangchen* @date 2020/11/12 */ class Builder99 extends GroovyTestCase{ void testNormalInstance(){ def c = new Chemist(first: "jack", last: "mar", born: 1822) } void testUseBuilderInstance(){ def builder = Chemist.builder() def c = builder.first('jack').last('mar').born(1867).build() assert c.first == 'jack' } } @Builder class Chemist{ String first, last int born }
-
2.2、Class设计和设计模式注解
-
@Canonical = @ToString, @EqualsAndHashCode, @TupleConstructor
-
import groovy.test.GroovyTestCase import groovy.transform.Canonical /** * 集合注解 @Canonical = @ToString, @EqualsAndHashCode, @TupleConstructor * @author liangchen* @date 2020/11/12 */ class Canonical910 extends GroovyTestCase{ void testCanonical(){ def i1 = new Inventor('Thomas', 'Edison') def i2 = new Inventor('Thomas') assert i1 != i2 assert i1.firstName == i2.firstName assert i1.toString()=='com.jack.groovy.ch9.Inventor(Thomas, Edison)' } } @Canonical class Inventor{ String firstName, lastName }
-
-
@Immutable (实例化后属性不能变更)
-
/** * 一旦实例化属性就不能修改 * @author liangchen* @date 2020/11/12 */ class Immutable911 extends GroovyTestCase{ void testImmutable(){ def g1 = new Genius(firstName: 'Albert',lastName: 'Einstein') assert g1.toString() == 'com.jack.groovy.ch9.Genius(Albert, Einstein)' def g2 = new Genius('Leonardo', 'da Vinci') assert g2.firstName == 'Leonardo' assert g1 != g2 shouldFail (ReadOnlyPropertyException) { g2.lastName = 'DiCaprio' } } } @Immutable class Genius{ String firstName, lastName }
-
-
@Delegate 委托能力
-
import groovy.test.GroovyTestCase /** * @Delegate 代理、委托 * @author liangchen* @date 2020/11/12 */ class NoisySet912 extends GroovyTestCase { void testDelegate(){ Set ns = new NoisySet(); ns.add(1) ns.addAll([2,3]) assert ns.size() == 3 } } /** * 如果某个类加了@Delegate,具有委托类能力 */ class NoisySet { @Delegate Set delegate = new HashSet() @Delegate Map mapDelegate = new HashMap() @Override boolean add(item) { println "adding $item" delegate.add(item) } boolean addAll(Collection items) { items.each {println "adding $it"} delegate.addAll(items) } put(key,value) { mapDelegate.put(key, value) } }
-
-
@Singleton 单例
-
package com.jack.groovy.ch9 import groovy.test.GroovyTestCase /** * @author liangchen* @date 2020/11/12 */ class Single913 extends GroovyTestCase { void testSingletonInstance(){ assert Zeus.instance def ex = shouldFail (RuntimeException){ new Zeus() } assert ex.toString()== "Can't instantiate singleton com.jack.groovy.ch9.Zeus. Use com.jack.groovy.ch9.Zeus.instance" } } @Singleton class Zeus{}
-
-
@Memoized 缓存方法结果
-
import groovy.test.GroovyTestCase import groovy.transform.Memoized /** * 缓存数据 * @author liangchen* @date 2020/11/12 */ class Memoized914 extends GroovyTestCase { void testMemoized(){ new Calc().with { assert sum(3,4) == 7 assert sum(4,4) == 8 //查询缓存 assert sum(3,4) == 7 assert sum(4,4) == 8 assert logs.join(" ") == "3+4 4+4" } } } class Calc { def logs = [] @Memoized int sum(int a, int b) { logs << "$a+$b" a + b } }
-
-
@TailRecursive 尾部递归,避免stack异常
-
import groovy.test.GroovyTestCase import groovy.transform.TailRecursive /** * @author liangchen* @date 2020/11/12 */ class TailRecursive915 extends GroovyTestCase{ void testTailRecursive(){ assert ListUtil.reverse(['1','2','3']) == ['3','2','1'] } } class ListUtil{ static reverse(List list) { doReverse(list,[]) } @TailRecursive private static doReverse(List todo, List done) { if(todo.isEmpty()) done else doReverse(todo.tail(), [todo.head()] + done) } }
-
2.3、日志提高
-
日志注解
-
/** * @Log注解 * @author liangchen* @date 2020/11/12 */ class Log916 extends GroovyTestCase{ void testLog(){ new Logs().search() } } @Log class Logs{ def search(){ log.fine(runLongDatabaseQuery()) } def runLongDatabaseQuery() { println 'Calling database' return 'query result' } }
-
-
其他注解
- @Log @Commons @ Log4j @Log4j2 @Slf4j
2.4、声明并发
-
@Synchronized 同步 @WithReadLock 和@WithWriteLock (读锁和写锁)
-
package com.jack.groovy.ch9 import com.beust.jcommander.IValueValidator import groovy.test.GroovyTestCase import groovy.transform.Synchronized import groovy.transform.WithReadLock import groovy.transform.WithWriteLock import groovy.util.logging.Log /** * @author liangchen* @date 2020/11/12 */ class Synchronized917 extends GroovyTestCase{ void testSynchronized(){ def p1 = new PhoneBook1() (0..99).collect{num -> //开启一个线程 Thread.start { p1.addNumber('Number' + num, '98765' + num) } //主函数等待 }*.join() assert p1.getNumber('Number43') == '9876543' } void testCustomsSynchronized(){ def p2 = new PhoneBook2() (0..99).collect{num -> Thread.start { p2.addNumber('Number' +num, '98765' + num) } }*.join() assert p2.getNumber('Number43') == '9876543' } void testReadAndWriteLock(){ def p3 = new PhoneBook3() (3..4).collect{num -> Thread.start { sleep 100*num p3.addNumber('Number' +num, '98765' + num) } } (2..7).collect{count -> Thread.start { sleep 100 * count p3.getNumber('Number'+ count) } }*.join() } } class PhoneBook1{ private final phoneNumbers = [:] @Synchronized def getNumber(key) { phoneNumbers[key] } @Synchronized void addNumber(key, value) { phoneNumbers[key] = value } } /** * 自定义锁块 */ @Log class PhoneBook2 { private final phoneNumbers = [:] private final lock = new Object[0] @Synchronized('lock') def getNumber(key) { phoneNumbers[key] } void addNumber(key, value) { log.info("Adding phone number $value") synchronized (lock) { phoneNumbers[key] = value } } } /** * 读写锁 */ class PhoneBook3 { private final phoneNumbers = dummyNums() private dummyNums() { (1..8).collectEntries{ ['Number'+it, '765432' + it] } } @WithReadLock def getNumber(key) { println "Reading started for $key" phoneNumbers[key] sleep 80 println "Reading done for $key" } @WithWriteLock def addNumber(key,value){ println "Writing started for $key" phoneNumbers[key] = value sleep 100 println "Writing done for $key" } }
-
2.5、容易克隆和扩展
-
@AutoClone
-
import groovy.test.GroovyTestCase import groovy.transform.AutoClone import groovy.transform.AutoCloneStyle import groovy.transform.TupleConstructor /** * @AutoClone 克隆注解 * * @author liangchen* @date 2020/11/13 */ class AutoClone720 extends GroovyTestCase{ void testAutoClone(){ def name = "Heston Blumenthal" def recipes =["Snail porridge","Bacon & egg ice cream"] def born = Date.parse("yyyy-MM-dd", '1999-01-01') def c1 = new Chef1( name, recipes, born) def c2 = c1.clone() assert c2.recipes == recipes assert c1.name == c2.name } void testAutoCopyConstructor() { def name = 'Jamie Oliver' def recipes = ['Lentil Soup', 'Crispy Duck'] def born = Date.parse('yyyy-MM-dd', '1975-05-27') def c1 = new Chef2(name, born, recipes) def c2 = c1.clone() assert c2.name == name assert c2.born == born assert c2.recipes == recipes } } /** * 提供四种类型, CLONE:不提供深度拷贝,不适合final修饰属性 * SIMPLE : 只会调用无参构造方法,不支持深度拷贝 * COPY_CONSTRUCTOR 不支持深度拷贝,支持final修饰属性 * SERIALIZATION : 支持深度拷贝,不适合final修饰属性拷贝 */ @AutoClone class Chef1{ String name List<String> recipes Date born } @TupleConstructor @AutoClone(style=AutoCloneStyle.COPY_CONSTRUCTOR) class Person { final String name final Date born } @TupleConstructor(includeSuperProperties=true, callSuper=true) @AutoClone(style=AutoCloneStyle.COPY_CONSTRUCTOR) class Chef2 extends Person { final List<String> recipes }
-
-
@AutoExternalize
-
import groovy.test.GroovyTestCase import groovy.transform.ToString /** * @AutoExternal 序列化 * @author liangchen* @date 2020/11/13 */ class AutoExternalize922 extends GroovyTestCase{ void testAutoExternalize() { def c = new Composer(name: 'Wolfgang Amadeus Mozart', born: 1756, married: true) //创建输出数组流 def baos = new ByteArrayOutputStream() //写出流到baos中 baos.withObjectOutputStream { os -> os.writeObject(c) } // 创建一个输入流 def bais = new ByteArrayInputStream(baos.toByteArray()) def loader = getClass().classLoader def result bais.withObjectInputStream(loader) { result = it.readObject().toString() } assert result == 'com.jack.groovy.ch9.Composer(Wolfgang Amadeus Mozart, 1756, true)' } } @groovy.transform.AutoExternalize @ToString class Composer{ String name int born boolean married }
-
2.6、脚本支持
-
@TimedInterrupt ( 超过时间打断)
-
/** * @author liangchen* @date 2020/11/13 */ class TimedInterrupt923 extends GroovyTestCase{ void testBlastOff1(){ def b = new BlastOff1() Thread.start { try { b.countdown(10) } catch (TimeoutException ignore) { b.logs << 'aborted' } }.join() assert b.logs.join(' ') == '10 9 8 7 6 aborted' } } /** * 超过480毫米自动打断方法,触发timeInterrupt */ @TimedInterrupt(value = 480L,unit = TimeUnit.MILLISECONDS) class BlastOff1 { def logs = [] def countdown(n) { sleep 100 logs << n if (n == 0) { logs << 'ignition' }else countdown(n -1) } }
-
-
@ThreadInterrupt 线程打断
-
import groovy.test.GroovyTestCase import groovy.transform.ThreadInterrupt /** * 线程打断, 这里例子有问题 * @author liangchen* @date 2020/11/13 */ class ThreadInterrupt924 extends GroovyTestCase{ void testThreadInterrupt(){ def b = new BlastOff2() def t1 = Thread.start { try { println 1 b.countdown(10) println 2 } catch (InterruptedException ignore) { b.logs << 'aborted' } } sleep 300 println 3 t1.interrupt() t1.join() assert b.logs.join(' ')=='10 9 8 7 6 5 4 3 2 1 0 ignition' } } @ThreadInterrupt() class BlastOff2 { def logs = [] def countdown(n) { sleep 100 logs << n if (n == 0) { logs << 'ignition' }else countdown(n -1) } }
-
-
@Field注解 脚本使用
-
import groovy.transform.Field /** * 不需要创建类似class文件结构只要是groovy就行 */ @Field List awe = [1,2,3] def awesum(){ awe.sum() } assert awesum() == 6
-
-
@BaseScript
2.7、更多转换
3、探索AST
- 一般groovy编译和运行过程
4、创建ASTs
4.1、亲手创建Ast
-
利用groovy已有AST的node创建新节点
-
import groovy.test.GroovyTestCase import org.codehaus.groovy.ast.ClassHelper import org.codehaus.groovy.ast.expr.ArgumentListExpression import org.codehaus.groovy.ast.expr.ConstructorCallExpression import org.codehaus.groovy.ast.stmt.ReturnStatement import static org.codehaus.groovy.ast.ClassHelper.make import static org.codehaus.groovy.ast.tools.GeneralUtils.* /** * @author liangchen* @date 2020/11/13 */ class Ast928 extends GroovyTestCase{ void testCreateAst(){ def ast = new ReturnStatement( new ConstructorCallExpression( ClassHelper.make(Date), ArgumentListExpression.EMPTY_ARGUMENTS ) ) assert ast instanceof ReturnStatement //创建语法树返回再抽象 def asts = returnS(ctorX(make(Date))) assert asts instanceof ReturnStatement } }
-
4.2、AstBuilder.buildFromSpec
-
利用规范快速构架AST结构
-
/** * 利用AstBuilder 快速构建 * @author liangchen* @date 2020/11/13 */ class AstBuilder930 extends GroovyTestCase{ void testAstBuilder(){ def ast = new AstBuilder().buildFromSpec { returnStatement { constructorCall(Date){ argumentList {} } } } assert ast[0] instanceof ReturnStatement } }
-
4.3、AstBuilder.buildFromString
-
利用字符串构建ast node
-
/** * 利用字符串直接创建Ast node * @author liangchen* @date 2020/11/13 */ class AstBuildFromString931 extends GroovyTestCase{ void testAstBuilderFromString(){ def ast = new AstBuilder().buildFromString('new Date()') assert ast[0] instanceof BlockStatement assert ast[0].statements[0] instanceof ReturnStatement } void testAstDynamicCode() { def approxPI = 3.14G // 直接添加方法,采用字符串 def ast = new AstBuilder().buildFromString( CompilePhase.CLASS_GENERATION, false, 'static double getTwoPI() { def pi = ' + approxPI + '; pi * 2 }' ) assert ast[1] instanceof ClassNode def method =ast[1].methods.find{ it.name == 'getTwoPI' } assert method instanceof MethodNode } }
-
4.4、AstBuilder.buildFromCode
-
利用code构建
-
/** * 利用code构建ast node * @author liangchen* @date 2020/11/13 */ class AstBuildFromCode944 extends GroovyTestCase{ void testAstBuildFromCode(){ def ast = new AstBuilder().buildFromCode { //直接使用代码 new Date() } assert ast[0].statements[0] instanceof ReturnStatement } }
-
5、本地转换
-
自定义main注解,自动生成main方法
-
package com.jack.groovy.ch9 import org.codehaus.groovy.ast.* import org.codehaus.groovy.control.CompilePhase import org.codehaus.groovy.control.SourceUnit import org.codehaus.groovy.transform.ASTTransformation import org.codehaus.groovy.transform.GroovyASTTransformation import org.codehaus.groovy.transform.GroovyASTTransformationClass import java.lang.annotation.ElementType import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy import java.lang.annotation.Target /** * 定义一下注解Main注解, 增加@GroovyASTransformationClass 表示这个注解处理的类 * 是MainTransformation * RetentionPolicy 注解保持到源码阶段 */ @Retention(RetentionPolicy.SOURCE) @Target([ElementType.METHOD]) @GroovyASTTransformationClass(classes = [MainTransformation]) @interface Main{} import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC import static groovyjarjarasm.asm.Opcodes.ACC_STATIC import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE import static org.codehaus.groovy.ast.tools.GeneralUtils.* // 确定那个时期处理,在指令选择处理 @GroovyASTTransformation(phase = CompilePhase.INSTRUCTION_SELECTION) class MainTransformation implements ASTTransformation{ // 定义默认节点 private NO_EXCEPTIONS = ClassNode.EMPTY_ARRAY private STRING_ARRAY = ClassHelper.STRING_TYPE.makeArray() @Override void visit(ASTNode[] astNodes, SourceUnit sourceUnit) { // 判断注解是否正确 if(astNodes?.size() != 2) return if(!(astNodes[0] instanceof AnnotationNode)) return if(astNodes[0].classNode.name != Main.name) return if(!(astNodes[1] instanceof MethodNode)) return // 获取目标方法、目标类,目标实例 def targetMethod = astNodes[1] def targetClass = targetMethod.declaringClass def targetInstance = ctorX(targetClass) def callTarget = callX(targetInstance, targetMethod.name) def mainBody = block(stmt(callTarget)) def visibility = ACC_STATIC | ACC_PUBLIC def parameters = params(param(STRING_ARRAY,'args')) //目标类增加main方法 targetClass.addMethod('main', visibility, VOID_TYPE, parameters,NO_EXCEPTIONS, mainBody) } } new GroovyShell(getClass().classLoader).evaluate """ class Greeter{ @com.jack.groovy.ch9.Main def greet(){ println "Hello from the greet() method!" } } """
-
6、全局转换
- 这个暂时不懂, 有点类似dubbo services, spi
7、测试AST转换
-
GroovyClassLoader 和 GroovyShell
-
package com.jack.groovy.ch9 import groovy.test.GroovyTestCase import groovy.transform.WithReadLock import java.lang.reflect.Modifier import java.util.concurrent.locks.ReentrantReadWriteLock /** * ast 测试 * @author liangchen* @date 2020/11/13 */ class WithReadLockTestAst938 extends GroovyTestCase{ static class MyClass{ @WithReadLock void readerMethod1(){} } void testLockFieldDefaultsForReadLock(){ def dee = MyClass.getDeclaredFields(); def field = MyClass.getDeclaredField('$reentrantlock') assert Modifier.isPrivate(field.modifiers) assert !Modifier.isTransient(field.modifiers) assert Modifier.isFinal(field.modifiers) assert !Modifier.isStatic(field.modifiers) assert field.type == ReentrantReadWriteLock } /** * 这是类,不是实例 */ void testLockFieldDefaultsForReadLocks(){ //字符串 直接解析类 def tester = new GroovyClassLoader().parseClass(""" class MyClass{ @groovy.transform.WithReadLock public void readerMethod1(){} } """) def field = tester.getDeclaredField('$reentrantlock') assert Modifier.isPrivate(field.modifiers) assert !Modifier.isTransient(field.modifiers) assert Modifier.isFinal(field.modifiers) assert !Modifier.isStatic(field.modifiers) assert field.type == ReentrantReadWriteLock } /** * groovyShell evaluate 是一个实例,而不是类 */ void testLockFieldDefaultsForReadLockGroovyShell() { def tester = new GroovyShell().evaluate(""" import groovy.transform.WithReadLock class MyClass{ @WithReadLock public void readerMethod1(){} } new MyClass() """) // 这里是getClass() def field = tester.getClass().getDeclaredField('$reentrantlock') assert Modifier.isPrivate(field.modifiers) assert !Modifier.isTransient(field.modifiers) assert Modifier.isFinal(field.modifiers) assert !Modifier.isStatic(field.modifiers) assert field.type == ReentrantReadWriteLock } }
-
-
@ASTTest
-
/** * @author liangchen* @date 2020/11/13 */ @ASTTest(phase = CompilePhase.SEMANTIC_ANALYSIS, value={ lookup('anchor').each{ n-> assert n instanceof ForStatement } }) void doSomenthing(){ println "Hello, Groovy" anchor:for(int i=0; i<10; i++){println "Iteration $i"} }
-
8、限制
10、总结
- 这一章有蒙圈,后面有需要再更新
最后
以上就是魔幻鸵鸟为你收集整理的第9章、编译期元编程和AST转换的全部内容,希望文章能够帮你解决第9章、编译期元编程和AST转换所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复