当前位置:天才代写 > tutorial > JAVA 教程 > Java 下一代: 没有担任性的扩展(三)

Java 下一代: 没有担任性的扩展(三)

2017-11-02 08:00 星期四 所属: JAVA 教程 浏览:726

副标题#e#

Groovy 元编程为您提供常见问题的简朴办理方案

Java 下一代语言扩揭示有的类和其他构件的要领有许多,前两期 Java 下一代 文章探讨了个中的一些要领。在本期文章中,我将继承该摸索,仔细查察在多种上下文中实现扩展的 Groovy 元编程技能。

在 “没有担任性的扩展,第 1  部门” 中,在接头利用种别类  和 ExpandoMetaClass 作为将新行为 “应用于” 现有类的机制时,我偶尔打仗了一些 Groovy 元编程特性。Groovy 中的元编程特性更深入一些:它们使得集成 Java 代码变得更容易,并且可以辅佐您回收比 Java 语言更简捷的方法来执行常见任务。

接口强制转换(Interface coercion)

接口是 Java 语言中常见的语义重用机制。实验以简捷的方法集成 Java 代码的其他语言应该提供简朴的要领来详细化接口。在 Groovy 中,类可以通过传统的 Java 方法来扩展接口。可是,Groovy 还使得在利便时轻松地将闭包和映射强制转换成接话柄例变得很容易。

单一要领强制转换

清单 1 中的 Java 代码示例利用 FilenameFilter 接口来定位文件:

清单 1. 在 Java 中利用    FilenameFilter 接口列出文件

import java.io.File;
import java.io.FilenameFilter;
    
public class ListDirectories {
    public String[] listDirectoryNames(String root) {
        return new File(root).list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return new File(name).isDirectory();
            }
        });
    }
}

在 清单 1 中,我建设了一个新的匿名内部类,它包围了指定过滤条件的 accept() 要领。在 Groovy 中,我可以跳过建设一个新类的步调,只将一个闭包强制转换成接口,如清单 2 所示:

清单 2. 在 Groovy 中通过利用闭包强制转换来模仿    FilenameFilter 接口

new File('.').list(
    { File dir, String name -> new File(name).isDirectory() }
     as FilenameFilter).each { println it }

在 清单 2 中,list() 要领想利用一个 FilenameFilter 实例作为参数。但我却建设了一个与接口的 accept() 签名相匹配的闭包,并在闭包的正文中实现接口的成果。在界说了闭包之后,我通过挪用 as  FilenameFilter 将闭包强制转换成适当的 FilenameFilter 实例。Groovy 的 as 运算符将闭包详细化为一个实现接口的类。该技能对付单一要领接口很是合用,因为要领和闭包之间存在一个自然映射。

对付指定多个要领的接口,被详细化的类为每个要领都挪用了沟通的闭包块。但只在少少数环境下,用沟通代码来处理惩罚所有要领挪用才是公道的。当您需要利用多个要领时,可以利用包括要领名称/闭包对的  Map,而不是利用单一的闭包。

映射

在 Groovy 中,还可以利用映射来暗示接口。映射的键是代表要领名称的字符串,键值是实现要领行为的代码块。清单 3 中的示例将一个映射详细化为一个  Iterator 实例:

清单 3. 在 Groovy 中利用映射来详细化接口

h = [hasNext:{ h.i > 0 }, next:{h.i--}]
h.i = 10
def iterator = h as Iterator
                                                      
while (iterator.hasNext())
  print iterator.next() + ", "
// 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,

在 清单 3 中,我建设了一个映射 (h),它包罗 hasNext 和  next 键,以及它们各自的代码块。Groovy 假设映射键是字符串,所以我不需要用引号来困绕该键。在每个代码块中,我用点标记  (h.i) 引用 h 映射的第三个键 (i)。这个点标记警惕自人们所熟悉的工具语法,它是 Groovy 中的另一个语法糖示例。在利用 h 作为一个迭代器之前,不会执行代码块,我必需首先确保 i 有一个值,然后再利用  h 作为一个迭代器。我用 h.i = 10 配置 i 的值。然后,我将 h 选作一个 Iterator,并利用从 10 开始的整数荟萃。

通过使得映射可以或许动态地作为接话柄例,Groovy 极大地淘汰了 Java 语言有时导致的一些语法问题。此特性很好地说明白 Java 下一代语言如何改造开拓人员的体验。

ExpandoMetaClass

正如我在 "没有担任性的扩展,第  1 部门" 中所述,您可以利用 ExpandoMetaClass 将新要领添加到类 — 包罗焦点类,好比  Object 和 String。ExpandoMetaClass 对付其他一些用途也是有用的,好比将要领添加到工具实例,以及改进异常处理惩罚。


#p#副标题#e#

将要领添加到工具和从工具中删除要领

从将行为附加到类的那一刻起,利用 ExpandoMetaClass 对类执行的变动就会在全局生效。普遍性是这种要领的优势 — 这并不奇怪,因为这种扩张机制源自 Grails Web 框架的建设。Grails 依赖于对焦点类的全局改观。但有时您需要在不影响所有实例的环境下,回收有限的方法为一个类添加语义。对付这些环境,Groovy 提供了可以与工具的元类实例 交互的方法。譬喻,您可以将要领只添加到某个特定的工具实例,如清单 4 所示:

#p#分页标题#e#

清单 4. 将行为附加到一个工具实例

def list = new ArrayList()
list.metaClass.randomize = { ->
    Collections.shuffle(delegate)
    delegate
}
    
list << 1 << 2 << 3 << 4
println list.randomize() // [2, 1, 4, 3]
println list             // [2, 1, 4, 3]

在 清单 4 中,我建设了 ArrayList 的一个实例 (list)。然后我会见了该实例以懒惰方法实例化的 metaClass 属性。我添加了一个要领  (randomize()),该要领返回执行 shuffle  之后的荟萃。在元类的要领声明中,delegate 代表工具实例。

不外,我的 randomize() 要领改变了底层荟萃,因为 shuffle() 是一个变异挪用。在 清单 4 的第二行输出中,请留意,该荟萃被永久性地变动为新的随机顺序。令人兴奋的是,通过办理这些问题,可以轻松地改变  Collections.shuffle() 等内置要领的默认行为。譬喻,清单 5 中的 random 属性是对 清单 4 的 randomize() 要领的改造:

清单 5. 改造不良语义

def list2 = new ArrayList()
list2.metaClass.getRandom = { ->
  def l = new ArrayList(delegate)
  Collections.shuffle(l)
  l
}
    
list2 << 1 << 2 << 3 << 4
println list2.random // [4, 1, 3, 2]
println list2        // [1, 2, 3, 4]

在 清单 5 中,我让 getRandom() 要领的正文先复制列表,然后再改变它,这样就可以让原始列表保持稳定。通过利用 Groovy 的定名约定,将属性自动映射到 get 和  set 要领,我让 random 也成为一个属性,而不是一个要领。

本栏目

利用属性技能来淘汰特另外括号滋扰,导致了最近在 Groovy 中将要领链接在一起的方法的改变。该语言的版本 1.8 引入了呼吁链 的观念,支持建设更流通的域特定语言(DSL)。DSL 凡是扩充现有的类或工具实例来添加非凡的行为。

殽杂

Ruby 和雷同语言中的一个风行特性是殽杂。殽杂让您可以或许不利用担任,而是将新的要领和字段添加到现有的条理布局中。Groovy 支持殽杂特性,如清单 6 所示:

清单 6. 利用殽杂特性来附加行为

class ListUtils {
  static randomize(List list) {
    def l = new ArrayList(delegate)
    Collections.shuffle(l)
    l
  }
}
List.metaClass.mixin ListUtils

在 清单 6 中,我建设了一个帮助类 (ListUtils) 并为其添加了一个  randomize() 要领。在最后一行中,我将 ListUtils 类与  java.util.List 殽杂在一起,让我的 randomize() 要领对  java.util.List 可用。也可以在工具实例中利用 mixin。这种技能通过将改观限制到某个单独的代码构件来辅佐执行调试和跟踪,所以,对付将行为附加到类而言,这是最好的方法。

团结扩展点

Groovy 的元编程特性不只在单独利用时很是强大,团结起来利用也很是有效。在动态语言中的一个常见细节是要领缺失(method missing) 钩 — 一个类可以或许以可控的方法响应尚未界说的要领,而不是抛出异常。假如呈现未知的要领挪用,Groovy 会在一个包括 methodMissing() 的类上挪用该要领。您可以在通过 ExpandoMetaClass 增加的附加物中包括 methodMissing()。通过团结利用  methodMissing() 与 ExpandoMetaClass,您可以使得 Logger 等现有的类越发机动。清单 7 显示了一个示例:

清单 7. 殽杂 ExpandoMetaClass 和    methodMissing

import java.util.logging.*
    
Logger.metaClass.methodMissing = { String name, args ->
    println "inside methodMissing with $name"
    int val = Level.WARNING.intValue() +
        (Level.SEVERE.intValue() - Level.WARNING.intValue()) * Math.random()
    def level = new CustomLevel(name.toUpperCase(),val)
    def impl = { Object... varArgs ->
        delegate.log(level,varArgs[0])
    }
    Logger.metaClass."$name" = impl
    impl args
}
    
Logger log = Logger.getLogger(this.class.name)
log.neal "really messed this up"
log.minor_mistake "can fix later"

在 清单 7 中,我利用 ExpandoMetaClass 将一个  methodMissing() 要领附加到 Logger 类。此刻,无论此 Logger 类在范畴中的哪个位置,我在今后的代码中都可以通过有创意的要领挪用日志,如 清单 7 中最后三行所示。

#p#副标题#e#

面向方面的编程

面向方面的编程(AOP)是一种风行的、实用的要领,可以逾越 Java 技能的原有设计对其举办扩展。通过哄骗字节码的编译进程,方面可以将新的代码 “编织” 到现有要领中。AOP 界说了一些术语,包罗 切入点(pointcut),这是执行增补的位置。譬喻,前 切入点是指在要领挪用前添加的代码。

#p#分页标题#e#

因为 Groovy 编译生成了 Java 字节码,所以在 Groovy 中也支持 AOP。但通过元编程可以在 Groovy 中复制 AOP,并且没有 Java 语言所要求的繁琐进程。 ExpandoMetaClass 使您可以或许会见一个要领,这样就无需引用该要领。之后,您可以从头界说该要领,也可以仍然挪用要领的原始版本。AOP 的这种 ExpandoMetaClass 用法如清单 8 所示:

清单 8. 对 ExpandoMetaClass  利用面向方面的切入点

class Bank {
  def transfer(Account to, Account from, BigDecimal amount) {
    from.balance -= amount
    to.balance += amount

  }
}
    
class Account {
  def name, balance;
    
  @Override
  public String toString() {
    "Account{name:${name}, balance:${balance}}"
  }
}
    
def oldTransfer = 
  Bank.metaClass.getMetaMethod("transfer", [Account, Account, BigDecimal] as Object[])
    
Bank.metaClass.transfer = { Account to, Account from, BigDecimal amount ->
  println "Logging transfer: to:${to}, from:${from}, amount:${amount}"
  oldTransfer.invoke(delegate, [to, from, amount] as Object[])
}
    
def bank = new Bank()
def acctA = new Account(name:"A", balance:100.00)
def acctB = new Account(name:"B", balance:200.00)
println("Balances:A = ${acctA.balance}, B = ${acctB.balance}")
bank.transfer(acctA, acctB, 10.00)
println("Balances:A = ${acctA.balance}, B = ${acctB.balance}")
//Balances:A = 100.00, B = 200.00
//Logging transfer: to:Account{name:A, balance:100.00},
//    from:Account{name:B, balance:200.00}, amount:10.00
//Balances:A = 110.00, B = 190.00

在 清单 8,我建设了一个典范的 Bank 类,它只有一个 transfer() 要领。帮助的 Account 类包括简朴的帐户信息。ExpandoMetaClass 包括一个  getMetaMethod()要领,用于检索对某个要领的引用。我利用了 清单 8 中的  getMetaMethod(),检索对现有 transfer() 要领的引用。然后,通过利用  ExpandoMetaClass,我建设了一个新的 transfer() 要领来代替旧的要领。在新要领的主体内,在写完日志语句后,我挪用了本来的要领。

清单 8 包括一个前切入点 示例:我执行了 “特另外” 代码,然后再挪用本来的要领。这在 Ruby 等动态语言中是一种常见的技能,其社区将该技能称为 Monkey Patching。(本来利用的术语是 Guerilla Patching,但它被错听为 Gorilla Patching,然后被改名为 Monkey Patching,就像是一种文字游戏。)其功效与 AOP 一样,但在 Groovy 中的动态扩展使您可以或许在语言自己内执行这个加强。

AST 转换

固然 ExpandoMetaClass 及其相关特性如此强大,它们也不能包围所有扩展点。最终,最强大的元编程可以或许修改编译器的 Abstract Syntax Tree(AST,抽象语法树) — 由编译历程维护的内部数据布局。注释是个中一种挂钩位置,在这里可以插入转换操纵。Groovy 预界说了一些有用的语言扩展,好比 AST 转换。

譬喻,@Lazy 注释(好比 @Lazy pets = [‘Cat’, ‘Dog’,  ‘Bird’])将数据布局的实例化推迟到必需评估它们的时候。Groovy 1.8 引入了一系列有用的布局性注释,个中一些会呈此刻清单 9 中:

清单 9. 在 Groovy 中有用的布局性注释

import groovy.transform.*;
    
@Immutable
@TupleConstructor
@EqualsAndHashCode
@ToString(includeNames = true, includeFields=true)
final class Point {
  int x
  int y
}

在 清单 9 中,Groovy 运行时会自动执行以下操纵:

生成元组气势气魄结构函数

生成 equals() 和 hashCode() 要领

使 Point 类不行变

生成一个 toString() 要领

利用 AST 调动远远优于利用 IDE 或反射来生成基本架构要领。在利用 IDE 的时候,假如产生变动,则必需始终紧记从头生成一些要领。而反射比在编译时产生的代码生成更慢。

除了利用富厚的预界说 AST 转换之外,还可以利用 Groovy 提供的一个完整 API 来构建本身的 AST 转换。通过这个 API,可以会见最细粒度的底层抽象,从而改变生成代码的方法。

竣事语

在本期文章中,您相识到 Groovy 通过其元编程特性提供的一系列令人目眩凌乱的扩展选项。在下一期 Java 下一代 文章中,我会摸索特征(殽杂成果)和 Scala 中的其他元编程。

本栏目

 

    关键字:

天才代写-代写联系方式