Android Gradle学习记录2 类及脚本的特点

本文探讨了Groovy脚本与Java类之间的交互方式,包括Groovy类的定义与导入、脚本编译原理及变量作用域等内容。通过具体实例展示了Groovy脚本如何与Java对象建立联系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇博客我们继续梳理一下Groovy的知识,
主要记录一下Groovy编写的脚本与Java类之间的联系。


1 Groovy定义类及import类
Groovy可以像Java那样定义类,例如:

package com.zhangjian.groovy

class Test {
    String mName
    String mTitle

    Test(name, title) {
        mName = name
        mTitle = title
    }

    def print() {
        println mName + ' ' + mTitle
    }
}

与Java不同的是,如果不声明public/private等访问权限,
Groovy中定义类的方法及成员变量均默认是public的。

与java一样,其它文件如果需要使用这个类时,
需要使用import关键字导入。

例如,在Test类的根目录下创建一个测试文件test.groovy时,
可以这么使用Test.groovy:

import com.zhangjian.groovy.Test

def test = new Test('superman', 'hero')
test.print()

现在,我们可以这么运行test.groovy:

结合上图,需要注意的是:
调用groovy命令时,它会自动加载当前路径对应目录及子目录下的xxx.groovy文件。
因此,当test.groovy中进行import的操作时,
groovy会认为当前目录下存在com/zhangjian/groovy/Test.java的文件。

我们测试一下,回到当前文件的上级目录,
直接运行test.groovy,会发现如下结果:

容易看出,如果我们在workspace的上级目录运行test.groovy时,
groovy无法从当前目录加载到com/zhangjian/groovy/Test.groovy文件。

此时,如果想加载到Test.groovy文件,就需要利用–classpath或-cp参数,
指定类所在路径,例如:


2 脚本的编译结果
我们知道Groovy编程可以像写脚本一样,将要做的事都写在xxx.groovy中,
然后,利用groovy xxx.groovy直接运行。

但在之前的博客中我们已经提到过,Groovy是基于Java的面向对象的编程语言。
Groovy到底如何将脚本与Java对象联系在一起的呢?

为了找到这个问题的答案,我们可以看一下Groovy文件编译的结果。

我们先创建一个简单的文件test.groovy,其内容如下:

def fun () {
    println 'have fun'
}

println 'Hello world'

容易看出,上面的代码定义了一个函数,并执行了一条命令。

接着,我们执行下列命令:

//-d后的参数指定的是:编译后文件的存放目录
//此处编译完的文件名是test.class
groovyc -d '/home/zhangjian/Desktop/temp' '/home/zhangjian/Desktop/test.groovy' 

编译完成后,我们可以利用jd-gui反编译test.class,得到下面的结果(此处保留了反编译后的原始式样):

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test
  extends Script
{
  public Object fun()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();return arrayOfCallSite[2].callCurrent(this, "have fun");return null;
  }

  public Object run()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();return arrayOfCallSite[1].callCurrent(this, "Hello world");return null;
  }

  public static void main(String... args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
  }

  public test(Binding context)
  {
    super(context);
  }

  public test() {}
}

从上面反编译后的代码可以看出:
1、test.groovy被转化成了一个test类,继承于Script类;

2、每一个Groovy脚本被编译后都会生成main函数,
当调用groovy xxx.groovy命令时,实际上就是利用java执行这个main函数;

3、脚本中的所有直接执行的代码都会放到run函数中,
可以看出当对象的main函数被调用后,会触发其run函数的运行;

4、如果脚本中定义了函数,那么这个函数将作为类的函数。

这里需要注意的是,假设我们这样写groovy脚本:

//注意这里定义函数,使用的是闭包
def fun = {
    println 'have fun'
}

println 'Hello world'

然后,同样利用groovyc来编译,我们发现生成了两个文件:
test.class和test$_run_closure1.class。

熟悉Java编译结果的朋友,应该能看出来,
此时生成了test内部类对应的class文件。

我们同样利用jd-gui观察一下test.class:

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test
  extends Script
{
  public test() {}

  public test(Binding context)
  {
    super(context);
  }

  public static void main(String... args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
  }

  //fun闭包对应一个内部类
  //这部分内容就是test$_run_closure1.class中的内容
  public class _run_closure1
    extends Closure
    implements GeneratedClosure
  {
    public _run_closure1(Object _thisObject)
    {
      super(_thisObject);
    }

    public Object doCall(Object it)
    {
      //包含闭包中的执行代码
      CallSite[] arrayOfCallSite = $getCallSiteArray();return arrayOfCallSite[0].callCurrent(this, "have fun");return null;
    }

    public Object doCall()
    {
      CallSite[] arrayOfCallSite = $getCallSiteArray();
      return doCall(null);
      return null;
    }
  }

  public Object run()
  {
    //在run中创建了闭包
    CallSite[] arrayOfCallSite = $getCallSiteArray();Object fun = new test._run_closure1(this);

    return arrayOfCallSite[1].callCurrent(this, "Hello world");return null;
  }
}

通过这个例子就可以看出,闭包虽然可以像函数一样被调用,
但在groovy中,它实际上是被当作一个内部类对象的。
这也印证了我们之前提到的,闭包同时具有函数和对象的特征。


3 脚本中的变量作用域
在本篇博客的最后,我们来研究一下脚本中定义变量的作用域。

定义一个简单的脚本文件,内容如下:

def x = 1

def printx() {
    println x
}

printx()

当我们运行脚本时,却出现如下错误:

Caught: groovy.lang.MissingPropertyException: No such property: x for class: test
groovy.lang.MissingPropertyException: No such property: x for class: test
    at test.printx(test.groovy:4)
    at test.run(test.groovy:7)

乍一看,这个异常让人有点难以理解,毕竟在脚本中我们已经显示定义了变量x。

为了理解这个问题,我们同样利用groovyc编译该脚本,
然后利用jd-gui看看编译后的结果:

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test
  extends Script
{
  //printx被定义为一个函数
  public Object printx()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();return arrayOfCallSite[2].callCurrent(this, arrayOfCallSite[3].callGroovyObjectGetProperty(this));return null;
  }

  public Object run()
  {
    //定义变量x的操作被放在run中进行, 是一个局部变量
    CallSite[] arrayOfCallSite = $getCallSiteArray();Object x = Integer.valueOf(1);
    if ((__$stMC) || (BytecodeInterface8.disabledStandardMetaClass())) {
      return arrayOfCallSite[1].callCurrent(this);
    } else {
      return printx();
    }
    return null;
  }

  public static void main(String... args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
  }

  public test(Binding context)
  {
    super(context);
  }

  public test() {}
}

从上面反编译的结果可以看出,出现异常的原因是x是一个局部变量。
与前文利用def定义闭包一样,定义的动作均被放至到run函数中进行。

此时,为了得到我们预期的结果,脚本必须这么写:

x = 1

def printx() {
    println x
}

printx()

我们看看这种写法的反编译结果:

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test
  extends Script
{
  public Object printx()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();return arrayOfCallSite[2].callCurrent(this, arrayOfCallSite[3].callGroovyObjectGetProperty(this));return null;
  }

  public Object run()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();int i = 
                                         //注意这个地方
1;ScriptBytecodeAdapter.setGroovyObjectProperty(Integer.valueOf(i), test.class, this, (String)"x");
    if ((__$stMC) || (BytecodeInterface8.disabledStandardMetaClass())) {
      return arrayOfCallSite[1].callCurrent(this);
    } else {
      return printx();
    }
    return null;
  }

  public static void main(String... args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
  }

  public test(Binding context)
  {
    super(context);
  }

  public test() {}
}

从反编译的结果来看,当按照上述脚本的方式定义x时,
x将作为一个属性,在run方法创建test对象时,被添加到test中。
于是,test对象中的printx方法可以获取到x的值。

注意到,此时x并不是test的成员变量。
我们创建一个新的脚本user.groovy,其内容如下:

// 创建之前的test对象
def user = new test()
//调用其printx函数
user.printx()

运行user.groovy时,仍然会抛出无法找到x的异常。
正如前文的分析,我们知道x是test.groovy脚本被调用时,
在其run方法中被动态添加到test对象的属性中。
如果脚本没有运行,即run方法没被调用时(没有调用脚本的main方法),
仅调用test对象的printx函数是获取不到x属性的。

说了这么多,当我们需要时,
到底该如何创建test对象的成员变量了?
类似的解决方案如下:

import groovy.transform.Field

//在定义的变量前,增加@Field标注
//这样x才成为test的成员变量
@Field x = 1

def printx() {
    println x
}

printx()

我们看看反编译的结果:

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test
  extends Script
{
  //成为成员变量
  Object x;

  public Object printx()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();return arrayOfCallSite[2].callCurrent(this, this.x);return null;
  }

  public Object run()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();null;
    if ((__$stMC) || (BytecodeInterface8.disabledStandardMetaClass())) {
      return arrayOfCallSite[1].callCurrent(this);
    } else {
      return printx();
    }
    return null;
  }

  public static void main(String... args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
  }

  public test(Binding context)
  {
    super(context);
    int i = 1;
    this.x = Integer.valueOf(i);
  }

  public test() {}
}

这样其它脚本创建出test对象后,就可以直接访问x了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值