返回介绍

1. 运行时元编程

发布于 2025-01-04 00:44:51 字数 23459 浏览 0 评论 0 收藏 0

运行时元编程,可以将一些决策(诸如解析、注入甚至合成类和接口的方法)推迟到运行时来完成。为了深入了解 Groovy 的 MOP,我们需要理解 Groovy 的对象以及 Groovy 处理方法。在 Groovy 中,我们主要与三类对象打交道:POJO、POGO,还有 Groovy 拦截器。Groovy 的元编程支持所有类型的对象,但是它们采用的方式却各不相同。

  • POJO —— 普通的 Java 对象,它的类可以用 Java 或其他任何 JVM 上的语言来编写。
  • POGO —— Groovy 对象,它的类使用 Groovy 编写而成,继承自 java.lang.Object 且默认实现了 groovy.lang.GroovyObject 接口。
  • Groovy 拦截器 —— 实现了 groovy.lang.GroovyInterceptable 接口的 Groovy 对象,并具有方法拦截功能。稍后将在 GroovyInterceptable 一节中详细介绍。

每当调用一个方法时,Groovy 会判断该方法是 POJO 还是 POGO。对于 POJO 对象,Groovy 会从 groovy.lang.MetaClassRegistry 读取它的 MetaClass ,并委托方法调用;对于 POGO 对象,Groovy 将要采取更多的执行步骤,如下图所示:

Groovy 拦截机制

图 1 Groovy 拦截机制

1.1 GroovyObject 接口

groovy.lang.GroovyObject 是 Groovy 中的关键接口,地位类似于 Java 中的 Object 类。在 groovy.lang.GroovyObjectSupport 类中有一个 GroovyObject 的默认实现,负责将调用传输给 groovy.lang.MetaClass 对象。 GroovyObject 源看起来如下所示:

package groovy.lang;

public interface GroovyObject {

  Object invokeMethod(String name, Object args);

  Object getProperty(String propertyName);

  void setProperty(String propertyName, Object newValue);

  MetaClass getMetaClass();

  void setMetaClass(MetaClass metaClass);
}

1.1.1 invokeMethod

根据 运行时元编程 的 Schema,当你所调用的方法没有在 Groovy 对象中提供的时候,调用该方法。下面这个例子中,使用了一个重写的 invokeMethod() 方法:

class SomeGroovyClass {

  def invokeMethod(String name, Object args) {
    return "called invokeMethod $name $args"
  }

  def test() {
    return 'method exists'
  }
}

def someGroovyClass = new SomeGroovyClass()

assert someGroovyClass.test() == 'method exists'
assert someGroovyClass.someMethod() == 'called invokeMethod someMethod []'

1.1.2 getPropertysetProperty

每次对属性的读取都可以通过重写当前对象的 getProperty() 来拦截,下面是一个简单的例子:


class SomeGroovyClass {

  def property1 = 'ha'
  def field2 = 'ho'
  def field4 = 'hu'

  def getField1() {
    return 'getHa'
  }

  def getProperty(String name) {
    if (name != 'field3')
      return metaClass.getProperty(this, name)   // 1⃣️   
    else
      return 'field3'
  }
}

def someGroovyClass = new SomeGroovyClass()

assert someGroovyClass.field1 == 'getHa'   
assert someGroovyClass.field2 == 'ho'  
assert someGroovyClass.field3 == 'field3'  
assert someGroovyClass.field4 == 'hu'   

1.1.3 getMetaClasssetMetaClass

可以访问一个对象 metaClass ,或者自定义 MetaClass 实现来改变默认的拦截机制。比如,你可以自己编写 MetaClass 接口的实现,并将它赋予对象,从而改变拦截机制。

// getMetaclass
someObject.metaClass

// setMetaClass
someObject.metaClass = new OwnMetaClassImplementation()

你可以在下文的 GroovyInterceptable 主题中看到更多的范例。

1.2 get/setAttribute

该功能与 MetaClass 实现有关。在默认的实现中,可以不用调用 getter 与 setter 而访问字段。下列例子就反映了这种方法。

class SomeGroovyClass {

  def field1 = 'ha'
  def field2 = 'ho'

  def getField1() {
    return 'getHa'
  }
}

def someGroovyClass = new SomeGroovyClass()

assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field1') == 'ha'
assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field2') == 'ho'
class POGO {

  private String field
  String property1

  void setProperty1(String property1) {
    this.property1 = "setProperty1"
  }
}

def pogo = new POGO()
pogo.metaClass.setAttribute(pogo, 'field', 'ha')
pogo.metaClass.setAttribute(pogo, 'property1', 'ho')

assert pogo.field == 'ha'
assert pogo.property1 == 'ho'

1.3 methodMissing

Groovy 支持 methodMissing 这一概念。该方法与 invokeMethod 的不同之处在于:只有当方法分派失败,找不到指定名称或带有指定实参的方法时,才会调用该方法。

class Foo {

   def methodMissing(String name, def args) {
    return "this is me"
   }
}

assert new Foo().someUnknownMethod(42l) == 'this is me'

通常,在使用 methodMissing 时,可能会将结果缓存起来,以备下次调用同样方法时使用。

比如像下面这样在 GORM 类中的动态查找器。它们是根据 methodMissing 来实现的:

class GORM {

   def dynamicMethods = [...] // 一些利用正则表达式的动态方法  

   def methodMissing(String name, args) {
     def method = dynamicMethods.find { it.match(name) }
     if(method) {
      GORM.metaClass."$name" = { Object[] varArgs ->
       method.invoke(delegate, name, varArgs)
      }
      return method.invoke(delegate,name, args)
     }
     else throw new MissingMethodException(name, delegate, args)
   }
}

注意,假如找到一个调用的方法,就会立刻使用 ExpandoMetaClass 动态地注册一个新方法。这样当下次调用同一方法时就会更方便。使用 methodMissing ,并不会产生像调用 invokeMethod 那么大的开销,第二次调用代价也并不昂贵。

1.4 propertyMissing

Groovy 支持 propertyMissing 的概念,用来拦截失败的属性解析尝试。对 getter 方法而言, propertyMissing 接受一个包含属性名的 String 参数:

class Foo {
   def propertyMissing(String name) { name }
}

assert new Foo().boo == 'boo'

当 Groovy 运行时无法找到指定属性的 getter 方法时,才会调用 propertyMissing(String) 方法。

对于 setter 方法,可以添加第二个 propertyMissing 定义来接收一个附加值参数。

class Foo {
   def storage = [:]
   def propertyMissing(String name, value) { storage[name] = value }
   def propertyMissing(String name) { storage[name] }
}

def f = new Foo()
f.foo = "bar"

assert f.foo == "bar"

对于 methodMissing 来说,最佳实践应该是在运行时动态注册新属性,从而改善总体的查找性能。

另外,处理静态方法和属性的 methodMissingpropertyMissing 方法可以通过 ExpandoMetaClass 来添加。

1.5 GroovyInterceptable

groovy.lang.GroovyInterceptable 接口是一种标记接口,继承自超接口 GroovyObject ,用于通知 Groovy 运行时通过方法分派器机制时应拦截的方法。

package groovy.lang;

public interface GroovyInterceptable extends GroovyObject {
}

当 Groovy 对象实现了 GroovyInterceptable 接口时,它的 invokeMethod() 方法就会在任何方法调用时调用。

下面就列举一个这种类型的方法:

class Interception implements GroovyInterceptable {

  def definedMethod() { }

  def invokeMethod(String name, Object args) {
    'invokedMethod'
  }
}

下面这段代码测试显示,无论方法是否存在,调用方法都将返回同样的值。

class InterceptableTest extends GroovyTestCase {

  void testCheckInterception() {
    def interception = new Interception()

    assert interception.definedMethod() == 'invokedMethod'
    assert interception.someMethod() == 'invokedMethod'
  }
}

我们不能使用默认的 Groovy 方法(比如 println ),因为这些方法已经被注入到了 Groovy 所有的对象中,自然会被拦截。

如果想要拦截所有的方法调用,但又不想实现 GroovyInterceptable 这个接口,那么我们可以在一个对象的 MetaClass 上实现 invokeMethod() 。该方法同时适于 POGO 与 POJO 对象,如下所示:

class InterceptionThroughMetaClassTest extends GroovyTestCase {

  void testPOJOMetaClassInterception() {
    String invoking = 'ha'
    invoking.metaClass.invokeMethod = { String name, Object args ->
      'invoked'
    }

    assert invoking.length() == 'invoked'
    assert invoking.someMethod() == 'invoked'
  }

  void testPOGOMetaClassInterception() {
    Entity entity = new Entity('Hello')
    entity.metaClass.invokeMethod = { String name, Object args ->
      'invoked'
    }

    assert entity.build(new Object()) == 'invoked'
    assert entity.someMethod() == 'invoked'
  }
}

参看 MetaClasses 一节内容了解 MetaClass 的更多内容。

1.6 类别(Categories)

如果一个不受控制的类有额外的方法,在某些情况下反而是有用的。为了实现这种功能,Groovy 从 Objective-C 那里借用并实现了一个概念,叫做: 类别Categories)。

类别功能是利用 类别类category classes)来实现的。类别类的特殊之处在于,需要遵循特定的预定义规则才能定义扩展方法。

系统已经包括了一些类别,可以为类添加相应功能,从而使它们在 Groovy 环境中更为实用。

类别类默认是不能启用的。要想使用定义在类别类中的方法,必须要使用 GDK 所提供的 use 范围方法,并且可用于每一个 Groovy 对象实例内部。

use(TimeCategory)  {
  println 1.minute.from.now   //1⃣️     
  println 10.hours.ago

  def someDate = new Date()  //2⃣️  
  println someDate - 3.months
}

1⃣️ TimeCategoryInteger 添加了方法
2⃣️ TimeCategoryDate 添加了方法

use 方法将类别类作为第一个形式参数,将一个闭包代码段作为第二个形式参数。在 Category 中,可以访问类别的任何方法。如上述代码所示,甚至 JDK 的 java.lang.Integerjava.util.Date 类都可以通过用户定义方法来丰富与增强。

类别不需要直接暴露给用户代码,如下所示:

class JPACategory{
  // 下面让我们无需通过 JSR 委员会的支持来增强 JPA EntityManager   
  static void persistAll(EntityManager em , Object[] entities) { //添加一个接口保存所有   
  entities?.each { em.persist(it) }
  }
}

def transactionContext = {
  EntityManager em, Closure c ->
  def tx = em.transaction
  try {
  tx.begin()
  use(JPACategory) {
    c()
  }
  tx.commit()
  } catch (e) {
  tx.rollback()
  } finally {
  //清除所有资源   
  }
}

// 用户代码。他们经常会在出现异常时忘记关闭资源,有些甚至会忘记提交,所以不能指望他们。
EntityManager em; //probably injected
transactionContext (em) {
 em.persistAll(obj1, obj2, obj3)
 // 在这里制定一些逻辑代码,使范例更合理。  
 em.persistAll(obj2, obj4, obj6)
}

通过查看 groovy.time.TimeCategory 类,我们就会发现,扩展方法都声明为 static 方法。实际上,要想使类别类的方法能成功地添加到 use 代码段内的类中,这是类别类必须满足的条件之一。

public class TimeCategory {

  public static Date plus(final Date date, final BaseDuration duration) {
    return duration.plus(date);
  }

  public static Date minus(final Date date, final BaseDuration duration) {
    final Calendar cal = Calendar.getInstance();

    cal.setTime(date);
    cal.add(Calendar.YEAR, -duration.getYears());
    cal.add(Calendar.MONTH, -duration.getMonths());
    cal.add(Calendar.DAY_OF_YEAR, -duration.getDays());
    cal.add(Calendar.HOUR_OF_DAY, -duration.getHours());
    cal.add(Calendar.MINUTE, -duration.getMinutes());
    cal.add(Calendar.SECOND, -duration.getSeconds());
    cal.add(Calendar.MILLISECOND, -duration.getMillis());

    return cal.getTime();
  }

  // ...

另外一个必备条件是静态方法的第一个实参必须定义方法一旦被启用时,该方法所连接的类型;而另一个实参则是常见的方法用于形参的实参。

由于形参和静态方法的规范,类别方法定义可能会比普通方法定义稍微不太直观。因此,作为替代方案,Groovy 引入了 @Category 标记,利用这一标记,可在编译时将标注的类转化为类别类。

class Distance {
  def number
  String toString() { "${number}m" }
}

@Category(Number)
class NumberCategory {
  Distance getMeters() {
    new Distance(number: this)
  }
}

use (NumberCategory)  {
  assert 42.meters.toString() == '42m'
}

使用 @Category 标记的优点在于,在使用实例方法时,可以不需要把目标类别当做第一个形参。目标类别类作为实参提供给标记使用。

关于 @Category 的另外介绍,可参看 编译时元编程

1.7 MetaClasses

(待定)

1.7.1 自定义 metaclass 类

(待定)

授权 metaclass

(待定)

魔法包(Magic package)

(待定)

1.7.2 每个实例的 metaclass

(待定)

1.7.3 ExpandoMetaClass

Groovy 提供了一种叫做 ExpandoMetaClass 的特殊 MetaClass 。其特殊之处在于,它允许可以使用灵活的闭包语法来动态添加或改变方法、构造函数、属性,甚至静态方法。

对于 测试向导 中所展示的模拟或存根情况,使用这些修改会特别有用。

每一个 Groovy 所提供的 java.lang.Class 都带有一个特殊的 metaClass 属性,它将提供一个 ExpandoMetaClass 实例的引用。该实例可用于添加方法或改变已有方法的行为。

默认情况下, ExpandoMetaClass 不支持继承。为了启用继承,必须在应用程序开始运作前(比如在 main 方法或 servlet bootstrap 中)就调用 ExpandoMetaClass#enableGlobally()

下面这些内容详细介绍了 ExpandoMetaClass 在不同情况下的应用。

方法

一旦通过调用 metaClass 属性访问了 ExpandoMetaClass ,就可以通过左移( << )或等于号( = )操作符来添加方法。

注意,左移操作符是用于 追加append)一个新的方法。如果方法已经存在,则会抛出一个异常。如果需要 替代replace)一个方法,则需要使用 = 操作符。

下例展示了操作符是如何应用于 metaClass 的一个不存在的属性上,从而传入 Closure 代码块的一个实例的。

class Book {
   String title
}

Book.metaClass.titleInUpperCase << {-> title.toUpperCase() }

def b = new Book(title:"The Stand")

assert "THE STAND" == b.titleInUpperCase()

上例显示,通过访问 metaClass 属性,可将一个新方法添加到一个类上,并可使用 <<= 操作符来指定一个 Closure 代码块。 Closure 形参被解析为方法形参。形参方法可以通过 {→ …​} 格式来添加。

属性

ExpandoMetaClass 支持两种方式来添加或重写属性。

首先,只需通过为 metaClass 赋予一个值,就可以声明一个 可变属性mutable property):

class Book {
   String title
}

Book.metaClass.author = "Stephen King"
def b = new Book()

assert "Stephen King" == b.author

另一个方式是,通过使用添加实例方法的标准机制来添加 getter 和(或)setter 方法:

class Book {
  String title
}
Book.metaClass.getAuthor << {-> "Stephen King" }

def b = new Book()

assert "Stephen King" == b.author

在上述源代码实例中,属性由闭包所指定,并且是一个只读属性。添加一个相等的 setter 方法也是可行的,但属性值需要存储起来以备后续使用。这种做法可以参照下面的例子:

class Book {
  String title
}

def properties = Collections.synchronizedMap([:])

Book.metaClass.setAuthor = { String value ->
   properties[System.identityHashCode(delegate) + "author"] = value
}
Book.metaClass.getAuthor = {->
   properties[System.identityHashCode(delegate) + "author"]
}

但这并不是唯一的办法。比如在一个 servlet 容器中,将当前执行请求中的值当作请求属性保存起来(就像 Grails 中的某些情况一样)。

构造函数

构造函数可以通过特殊的 constructor 属性来添加。 <<= 操作符都可以用于指定 Closure 代码段。当代码在运行时执行时, Closure 实参会成为构造函数的实参。

class Book {
  String title
}
Book.metaClass.constructor << { String title -> new Book(title:title) }

def book = new Book('Groovy in Action - 2nd Edition')
assert book.title == 'Groovy in Action - 2nd Edition'

但在添加构造函数时要格外注意,因为这极易造成栈溢出。

静态方法

添加静态方法的方法与添加实例方法基本一样,只不过要在方法名前加上 static 修饰符。

class Book {
   String title
}

Book.metaClass.static.create << { String title -> new Book(title:title) }

def b = Book.create("The Stand")

借用方法

利用 ExpandoMetaClass ,可以使用 Groovy 方法点标记法从其他类中借用方法。

class Person {
  String name
}
class MortgageLender {
   def borrowMoney() {
    "buy house"
   }
}

def lender = new MortgageLender()

Person.metaClass.buyHouse = lender.&borrowMoney

def p = new Person()

assert "buy house" == p.buyHouse()

动态方法名

在 Groovy 中,既然可以使用字符串作为属性名,那么反过来,也可以在运行时动态创建方法与属性名。要想创建具有动态名称的方法,只需使用将字符串引用为属性名的语言特性。

class Person {
   String name = "Fred"
}

def methodName = "Bob"

Person.metaClass."changeNameTo${methodName}" = {-> delegate.name = "Bob" }

def p = new Person()

assert "Fred" == p.name

p.changeNameToBob()

assert "Bob" == p.name

同样的概念可以应用于静态方法与属性。

Grails Web 应用框架可以算是动态方法名的一个应用范例。“动态编解码器”的概念正是通过动态方法名来实现的。

HTMLCodec

class HTMLCodec {
  static encode = { theTarget ->
    HtmlUtils.htmlEscape(theTarget.toString())
  }

  static decode = { theTarget ->
    HtmlUtils.htmlUnescape(theTarget.toString())
  }
}

上例实现了一个编解码器。Grails 提供了多种编解码器实现,每种实现都定义在一个类中。在运行时,会在应用类路径上出现多个编解码器类。在应用启动时,框架会将 encodeXXXdecodeXXX 方法添加到特定的元类中,这里的 XXX 是指编解码器类名的前面部分(如 encodeHTML )。下面采用了一些 Groovy 伪码来表示这种机制:

def codecs = classes.findAll { it.name.endsWith('Codec') }

codecs.each { codec ->
  Object.metaClass."encodeAs${codec.name-'Codec'}" = { codec.newInstance().encode(delegate) }
  Object.metaClass."decodeFrom${codec.name-'Codec'}" = { codec.newInstance().decode(delegate) }
}

def html = '<html><body>hello</body></html>'

assert '<html><body>hello</body></html>' == html.encodeAsHTML()

运行时发现

在运行时阶段执行某个方法时,还有其他什么方法或属性存在?这个问题往往是非常有用的。 ExpandoMetaClass 提供了下列方法(截止目前):

  • getMetaMethod
  • hasMetaMethod
  • getMetaProperty
  • hasMetaProperty

为什么不能单纯使用反射呢?因为 Groovy 的独特性——它包含两种方法,一种是“真正”的方法,而另一种则是只在运行时才能获取并使用的方法。后者有时(但也并不总是被)称为元方法(MetaMethods)。元方法告诉我们在运行时究竟能够使用何种方法,从而使代码能够适应。

这一点特别适用于重写 invokeMethodgetProperty 和/或 setProperty 时。

GroovyObject 方法

ExpandoMetaClass 的另一个特性是能够允许重写 invokeMethodgetPropertysetPropertygroovy.lang.GroovyObject 类中能找到这三个方法。

下面范例展示了如何重写 invokeMethod

class Stuff {
   def invokeMe() { "foo" }
}

Stuff.metaClass.invokeMethod = { String name, args ->
   def metaMethod = Stuff.metaClass.getMetaMethod(name, args)
   def result
   if(metaMethod) result = metaMethod.invoke(delegate,args)
   else {
    result = "bar"
   }
   result
}

def stf = new Stuff()

assert "foo" == stf.invokeMe()
assert "bar" == stf.doStuff()

重写静态方法的逻辑跟之前我们见过的重写实例方法的逻辑基本相同,唯一不同之处在于对 metaClass.static 属性的访问,以及为了获取静态 MetaMethod 实例而对 getStaticMethodName 的调用。

重写静态 invokeMethod

ExpandoMetaClass 甚至可以允许利用一种特殊的 invokeMethod 格式重写静态方法。

class Stuff {
   static invokeMe() { "foo" }
}

Stuff.metaClass.'static'.invokeMethod = { String name, args ->
   def metaMethod = Stuff.metaClass.getStaticMetaMethod(name, args)
   def result
   if(metaMethod) result = metaMethod.invoke(delegate,args)
   else {
    result = "bar"
   }
   result
}

assert "foo" == Stuff.invokeMe()
assert "bar" == Stuff.doStuff()

扩展接口

可以利用 ExpandoMetaClass 为接口添加方法,但要想这样做, 必须 在应用启动前使用 ExpandoMetaClass.enableGlobally() 方法实施全局启用。

List.metaClass.sizeDoubled = {-> delegate.size() * 2 }

def list = []

list << 1
list << 2

assert 4 == list.sizeDoubled()  

1.8 扩展模块

1.8.1 扩展现有类

利用扩展模块,可以为现有类添加新方法,这些类中可以包括 JDK 中那样的预编译类。这些新方法与通过元类或类别定义的方法不同,它们是全局可用的。比如当你编写:

标准扩展方法

def file = new File(...)
def contents = file.getText('utf-8')

File 类中并不存在 getText 方法,但 Groovy 知道它的定义是在一个特殊类中 ResourceGroovyMethods

ResourceGroovyMethods.java

public static String getText(File file, String charset) throws IOException {
 return IOGroovyMethods.getText(newReader(file, charset));
}

你可能还注意到扩展方法是在“辅助”类(定义了多种扩展方法)中通过一个静态方法来定义的。 getText 的第一个实参对应着接受者,而另一个形参则对应着扩展方法的实参。因此,我们才在 File 类(因为第一个实参是 File 类型)中定义了一个名为 getText 的方法,它只传递了一个实参( String 类型的编码)。

创建扩展模块的过程非常简单:

  • 如上例般编写扩展类;
  • 编写模块描述符文件。

然后,还必须让 Groovy 能找到该扩展模块,这只需将扩展模块类和描述符放入类路径即可。这意味着有以下两种方法:

  • 直接在类路径上提供类和模块描述符。
  • 将扩展模块打包为 jar 文件,便于重用。

扩展模块可以为类添加两种方法:

  • 实例方法(类实例上调用)
  • 静态方法(仅供类自身调用)

1.8.2 实例方法

为现有类添加实例方法,需要创建一个扩展类。比如想在 Integer 上加一个 maxRetries 方法,可以采取下面的方式:

MaxRetriesExtension.groovy

class MaxRetriesExtension {                   //1⃣️               
  static void maxRetries(Integer self, Closure code) {    //2⃣️   
    int retries = 0
    Throwable e
    while (retries<self) {
      try {
        code.call()
        break
      } catch (Throwable err) {
        e = err
        retries++
      }
    }
    if (retries==0 && e) {
      throw e
    }
  }
}

1⃣️ 扩展类
2⃣️ 静态方法的第一个实际参数对应着消息的接受者,也就是扩展实例。

然后, 在已经声明了扩展类之后 ,你可以这样调用它:

int i=0
5.maxRetries {
  i++
}
assert i == 1
i=0
try {
  5.maxRetries {
    throw new RuntimeException("oops")
  }
} catch (RuntimeException e) {
  assert i == 5
}

1.8.3 静态方法

也可以为类添加静态方法。这种情况下,静态方法需要在自己的文件中定义。

StaticStringExtension.groovy

class StaticStringExtension {    // 1⃣️                   
  static String greeting(String self) {   // 2⃣️              
    'Hello, world!'
  }
}

1⃣️ 静态扩展类 2⃣️ 静态方法的第一个实参对应着将要扩展并且 还未使用 的类

在这种情况下,可以直接在 String 类中调用它:

assert String.greeting() == 'Hello, world!'

1.8.4 模块描述符

为了使 Groovy 能够加载扩展方法,你必须声明扩展辅助类。必须在 META-INF/services 目录中创建一个名为 org.codehaus.groovy.runtime.ExtensionModule 的文件。

org.codehaus.groovy.runtime.ExtensionModule

moduleName=Test module for specifications
moduleVersion=1.0-test
extensionClasses=support.MaxRetriesExtension
staticExtensionClasses=support.StaticStringExtension

该模块描述符需要 4 个键:

  • moduleName:模块名称
  • moduleVersion:模块版本。注意,版本号只能用于检查是否将同一个模块加载了两种不同的版本。
  • extensionClasses:实例方法的扩展辅助类列表。可以提供几个类,但要用逗号分隔它们。
  • staticExtensionClasses:静态方法的扩展辅助类列表。可以提供几个类,也要用逗号分隔它们。

注意,模块并不一定要既能定义静态辅助类,又能定义实例辅助类。你可以在一个模块中添加几个类,也可以在单一模块中扩展不同的类,甚至还可以在单一的扩展类中使用不同的类,但强烈建议将扩展方法按功能集分入不同的类。

1.8.5 扩展模块和类路径

值得注意的是,不能在代码使用已编译扩展模块的时候,你无法使用它。这意味着,要想使用扩展模块,在将要使用它的代码被编译前,它就必须以已编译类的形式出现在类路径上。这其实就是说,与扩展类同一源单位中不能出现测试类(test class),然而,测试源通常在实际中与常规源是分开的,在构建的另一个步骤中执行,所以这根本不会造成任何不良影响。

1.8.6 类型检查的兼容性

与类别不同的是,扩展模块与类型检查是兼容的:如果在类路径上存在这些模块,类检查器就会知道扩展方法,并不会说明调用的时间。它们与静态编译也是兼容的。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文